From 0f3ca8050b78f0c5c1ef98d485b9fbc2ba06183c Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Sun, 23 Aug 2026 10:13:53 -0400 Subject: [PATCH] feat(field-support): added field support system, mid migration currently the barotope and the pressure force operator are migrated to the new support system --- CMakeLists.txt | 48 +- clang-format-styles/style | 2 +- libmeanfield/impl/analysis/integral.cpp | 103 +- libmeanfield/impl/fem.cpp | 359 +-- libmeanfield/impl/integrators/advection.cpp | 21 +- libmeanfield/impl/integrators/centrifugal.cpp | 10 +- libmeanfield/impl/integrators/coriolis.cpp | 16 +- libmeanfield/impl/integrators/gravity.cpp | 180 +- .../impl/integrators/mass_continuity.cpp | 121 +- libmeanfield/impl/integrators/viscosity.cpp | 10 +- libmeanfield/impl/mapping/coefficients.cpp | 5 +- .../impl/mapping/compactification/kelvin.cpp | 87 +- libmeanfield/impl/mapping/domain_mapper.cpp | 88 +- .../impl/mapping/domain_mapper_new.cpp | 404 +-- libmeanfield/impl/mapping/transformations.cpp | 88 +- libmeanfield/impl/models/polytropic.cpp | 260 ++ ...rotropic_closure_linearization_context.cpp | 225 +- .../contexts/gravity_field_context.cpp | 193 +- .../hydrostatic_equilibrium_context.cpp | 116 +- .../contexts/pressure_force_context.cpp | 219 ++ .../rotation_displacement_force_context.cpp | 203 ++ libmeanfield/impl/operators/gravity_field.cpp | 479 +-- .../impl/operators/gravity_field_jacobian.cpp | 185 +- .../kernels/barotropic_closure_kernels.cpp | 502 ++-- .../gravity_displacement_force_kernels.cpp | 718 +++++ .../operators/kernels/gravity_kernels.cpp | 667 ++--- .../hydrostatic_equilibrium_kernels.cpp | 405 +-- .../kernels/pressure_force_kernels.cpp | 468 +-- .../rotation_displacement_force_kernels.cpp | 590 ++++ .../operators/prepared_barotropic_closure.cpp | 693 ++--- .../prepared_displacement_operator.cpp | 554 ++++ .../prepared_gravity_displacement_force.cpp | 290 ++ .../operators/prepared_gravity_source.cpp | 337 +-- .../impl/operators/prepared_hdiv_mass.cpp | 232 +- .../prepared_hydrostatic_equilibrium.cpp | 781 ++--- .../operators/prepared_mass_normalization.cpp | 668 +++++ .../operators/prepared_pressure_force.cpp | 1137 ++++++++ .../prepared_rotation_displacement_force.cpp | 288 ++ .../prepared_stellar_equilibrium.cpp | 809 ++++++ libmeanfield/impl/physics/gravity.cpp | 334 +-- libmeanfield/impl/physics/solid.cpp | 30 +- libmeanfield/impl/utils/domain.cpp | 19 +- libmeanfield/impl/utils/misc.cpp | 28 +- libmeanfield/interface/analysis/integral.cppm | 10 +- libmeanfield/interface/boundary/context.cppm | 4 +- libmeanfield/interface/eos/eos_base.cppm | 16 + libmeanfield/interface/eos/polytropic.cppm | 170 ++ libmeanfield/interface/fem.cppm | 17 +- libmeanfield/interface/field/field_base.cppm | 189 +- libmeanfield/interface/field/field_mfem.cppm | 704 ++++- .../interface/field/field_registry.cppm | 229 +- .../interface/integrators/centrifugal.cppm | 3 +- .../interface/integrators/gravity.cppm | 12 +- .../integrators/mass_continuity.cppm | 3 +- .../integrators/pressure_gradient.cppm | 18 +- .../mapping/compactification/kelvin.cppm | 4 +- .../interface/mapping/domain_mapper.cppm | 34 +- libmeanfield/interface/mean_field.cppm | 18 + .../interface/models/stellar_model.cppm | 114 + .../models/structure/polytropic.cppm | 68 + .../models/structure/structure_base.cppm | 37 + .../interface/models/structure_profile.cppm | 146 + ...otropic_closure_linearization_context.cppm | 126 +- .../contexts/gravity_field_context.cppm | 46 +- .../hydrostatic_equilibrium_context.cppm | 53 +- .../contexts/pressure_force_context.cppm | 128 + .../rotation_displacement_force_context.cppm | 124 + .../interface/operators/gravity_field.cppm | 68 +- .../operators/gravity_field_jacobian.cppm | 11 +- .../kernels/barotropic_closure_kernels.cppm | 23 +- .../gravity_displacement_force_kernels.cppm | 59 + .../kernels/pressure_force_kernels.cppm | 24 +- .../rotation_displacement_force_kernels.cppm | 64 + .../prepared_barotropic_closure_operator.cppm | 72 +- .../prepared_displacement_operator.cppm | 198 ++ .../prepared_gravity_displacement_force.cppm | 150 + ...ared_hydrostatic_equilibrium_operator.cppm | 64 +- .../prepared_mass_normalization.cppm | 185 ++ .../operators/prepared_pressure_force.cppm | 304 ++ .../prepared_rotation_displacement_force.cppm | 149 + .../prepared_stellar_equilibrium.cppm | 177 ++ libmeanfield/interface/physics/barotrope.cppm | 31 +- .../interface/physics/rigid_rotation.cppm | 126 +- libmeanfield/interface/quadrature/mfem.cppm | 68 +- libmeanfield/interface/quadrature/policy.cppm | 55 +- libmeanfield/interface/surface/isobaric.cppm | 64 + .../interface/surface/surface_base.cppm | 53 + libmeanfield/interface/utils/blocks.cppm | 170 +- libmeanfield/interface/utils/domain.cppm | 1102 ++++++- libmeanfield/interface/utils/misc.cppm | 8 +- libmeanfield/interface/utils/user.cppm | 3 +- tests/field/field_base.cpp | 414 +++ tests/field/field_dof_map.cpp | 724 +++++ tests/field/field_mfem.cpp | 778 +++++ tests/field/field_registry.cpp | 427 +++ tests/integrators/centrifugal.cpp | 815 ++---- tests/integrators/gravity.cpp | 612 ++-- tests/mapping/compactification/kelvin.cpp | 464 +-- tests/mapping/domain_mapper.cpp | 1987 ++++--------- tests/mapping/hdiv_mass_tensor.cpp | 181 +- tests/models/stellar_model.cpp | 259 ++ ...rotropic_closure_linearization_context.cpp | 601 ++-- .../contexts/gravity_field_context.cpp | 194 +- .../hydrostatic_equilibrium_context.cpp | 133 +- .../contexts/pressure_force_context.cpp | 325 +++ .../rotation_displacement_force_context.cpp | 181 ++ .../operators/gravity_displacement_force.cpp | 710 +++++ ...isplacement_force_analytic_comparisons.cpp | 381 +++ tests/operators/gravity_field.cpp | 2560 ++++++----------- .../kernels/barotropic_closure_kernels.cpp | 382 +-- .../hydrostatic_equilibrium_kernels.cpp | 636 ++-- .../kernels/pressure_force_kernels.cpp | 545 +++- .../operators/prepared_barotropic_closure.cpp | 1474 +++++----- .../prepared_displacement_operator.cpp | 954 ++++++ tests/operators/prepared_gravity_source.cpp | 88 +- tests/operators/prepared_hdiv_mass.cpp | 88 +- .../prepared_hydrostatic_equilibrium.cpp | 139 +- ...rostatic_equilibrium_analytic_accuracy.cpp | 183 +- ...rostatic_equilibrium_complete_jacobian.cpp | 217 +- ...atic_equilibrium_displacement_jacobian.cpp | 234 +- ...pared_hydrostatic_equilibrium_jacobian.cpp | 212 +- .../operators/prepared_mass_normalization.cpp | 508 ++++ tests/operators/prepared_pressure_force.cpp | 706 +++++ .../prepared_rotation_displacement_force.cpp | 609 ++++ ..._displacement_force_affine_deformation.cpp | 303 ++ ...d_rotation_displacement_force_analytic.cpp | 256 ++ .../prepared_stellar_equilibrium.cpp | 2365 +++++++++++++++ tests/physics/barotrope.cpp | 65 +- tests/physics/barotrope_pressure.cpp | 409 +-- tests/physics/gravity.cpp | 2217 +++++--------- tests/physics/gravity_monopole_accuracy.cpp | 890 ++---- tests/quadrature/policy.cpp | 495 +--- tests/surface/isobaric.cpp | 76 + tests/test_helpers.cppm | 162 +- tests/test_main.cpp | 230 +- tests/utils/blocks.cpp | 567 ++-- tests/utils/domain.cpp | 1135 ++++++++ 137 files changed, 29975 insertions(+), 16389 deletions(-) create mode 100644 libmeanfield/impl/models/polytropic.cpp create mode 100644 libmeanfield/impl/operators/contexts/pressure_force_context.cpp create mode 100644 libmeanfield/impl/operators/contexts/rotation_displacement_force_context.cpp create mode 100644 libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp create mode 100644 libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp create mode 100644 libmeanfield/impl/operators/prepared_displacement_operator.cpp create mode 100644 libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp create mode 100644 libmeanfield/impl/operators/prepared_mass_normalization.cpp create mode 100644 libmeanfield/impl/operators/prepared_pressure_force.cpp create mode 100644 libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp create mode 100644 libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp create mode 100644 libmeanfield/interface/eos/eos_base.cppm create mode 100644 libmeanfield/interface/eos/polytropic.cppm create mode 100644 libmeanfield/interface/models/stellar_model.cppm create mode 100644 libmeanfield/interface/models/structure/polytropic.cppm create mode 100644 libmeanfield/interface/models/structure/structure_base.cppm create mode 100644 libmeanfield/interface/models/structure_profile.cppm create mode 100644 libmeanfield/interface/operators/contexts/pressure_force_context.cppm create mode 100644 libmeanfield/interface/operators/contexts/rotation_displacement_force_context.cppm create mode 100644 libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm create mode 100644 libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm create mode 100644 libmeanfield/interface/operators/prepared_displacement_operator.cppm create mode 100644 libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm create mode 100644 libmeanfield/interface/operators/prepared_mass_normalization.cppm create mode 100644 libmeanfield/interface/operators/prepared_pressure_force.cppm create mode 100644 libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm create mode 100644 libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm create mode 100644 libmeanfield/interface/surface/isobaric.cppm create mode 100644 libmeanfield/interface/surface/surface_base.cppm create mode 100644 tests/field/field_base.cpp create mode 100644 tests/field/field_dof_map.cpp create mode 100644 tests/field/field_mfem.cpp create mode 100644 tests/field/field_registry.cpp create mode 100644 tests/models/stellar_model.cpp create mode 100644 tests/operators/contexts/pressure_force_context.cpp create mode 100644 tests/operators/contexts/rotation_displacement_force_context.cpp create mode 100644 tests/operators/gravity_displacement_force.cpp create mode 100644 tests/operators/gravity_displacement_force_analytic_comparisons.cpp create mode 100644 tests/operators/prepared_displacement_operator.cpp create mode 100644 tests/operators/prepared_mass_normalization.cpp create mode 100644 tests/operators/prepared_pressure_force.cpp create mode 100644 tests/operators/prepared_rotation_displacement_force.cpp create mode 100644 tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp create mode 100644 tests/operators/prepared_rotation_displacement_force_analytic.cpp create mode 100644 tests/operators/prepared_stellar_equilibrium.cpp create mode 100644 tests/surface/isobaric.cpp create mode 100644 tests/utils/domain.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b30c177..9cca9ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,17 @@ target_sources(mean_field libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp + libmeanfield/impl/operators/contexts/pressure_force_context.cpp + libmeanfield/impl/operators/prepared_pressure_force.cpp + libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp + libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp + libmeanfield/impl/operators/contexts/rotation_displacement_force_context.cpp + libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp + libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp + libmeanfield/impl/operators/prepared_displacement_operator.cpp + libmeanfield/impl/models/polytropic.cpp + libmeanfield/impl/operators/prepared_mass_normalization.cpp + libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp ) target_sources(mean_field @@ -122,6 +133,24 @@ target_sources(mean_field libmeanfield/interface/operators/kernels/hydrostatic_equilibrium_kernels.cppm libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm + libmeanfield/interface/operators/contexts/pressure_force_context.cppm + libmeanfield/interface/operators/prepared_pressure_force.cppm + libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm + libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm + libmeanfield/interface/operators/contexts/rotation_displacement_force_context.cppm + libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm + libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm + libmeanfield/interface/operators/prepared_displacement_operator.cppm + libmeanfield/interface/eos/eos_base.cppm + libmeanfield/interface/eos/polytropic.cppm + libmeanfield/interface/models/structure/structure_base.cppm + libmeanfield/interface/models/structure/polytropic.cppm + libmeanfield/interface/models/structure_profile.cppm + libmeanfield/interface/surface/surface_base.cppm + libmeanfield/interface/surface/isobaric.cppm + libmeanfield/interface/models/stellar_model.cppm + libmeanfield/interface/operators/prepared_mass_normalization.cppm + libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm ) @@ -180,7 +209,24 @@ add_executable(tests tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp tests/physics/barotrope_pressure.cpp tests/operators/kernels/pressure_force_kernels.cpp - + tests/operators/contexts/pressure_force_context.cpp + tests/operators/prepared_pressure_force.cpp + tests/operators/gravity_displacement_force.cpp + tests/operators/gravity_displacement_force_analytic_comparisons.cpp + tests/operators/contexts/rotation_displacement_force_context.cpp + tests/operators/prepared_rotation_displacement_force.cpp + tests/operators/prepared_rotation_displacement_force_analytic.cpp + tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp + tests/operators/prepared_displacement_operator.cpp + tests/surface/isobaric.cpp + tests/models/stellar_model.cpp + tests/operators/prepared_mass_normalization.cpp + tests/operators/prepared_stellar_equilibrium.cpp + tests/utils/domain.cpp + tests/field/field_base.cpp + tests/field/field_registry.cpp + tests/field/field_mfem.cpp + tests/field/field_dof_map.cpp ) target_link_libraries(tests PRIVATE mean_field test_mod Catch2::Catch2 Boost::boost) diff --git a/clang-format-styles/style b/clang-format-styles/style index 31980cb..723de0f 100644 --- a/clang-format-styles/style +++ b/clang-format-styles/style @@ -128,7 +128,7 @@ BreakFunctionDefinitionParameters: false BreakInheritanceList: BeforeColon BreakStringLiterals: true BreakTemplateDeclarations: MultiLine -ColumnLimit: 80 +ColumnLimit: 120 CommentPragmas: "^ IWYU pragma:" CompactNamespaces: false ConstructorInitializerIndentWidth: 4 diff --git a/libmeanfield/impl/analysis/integral.cpp b/libmeanfield/impl/analysis/integral.cpp index 5dcfdf7..86e31b7 100644 --- a/libmeanfield/impl/analysis/integral.cpp +++ b/libmeanfield/impl/analysis/integral.cpp @@ -13,23 +13,16 @@ namespace { const std::array< int, FormT::dynamicOrderCount> &dynamic_orders = {}, - const mean_field::utils::DOMAINS domain = - mean_field::utils::DOMAINS::ALL + const mean_field::utils::DOMAINS domain = mean_field::utils::DOMAINS::ALL ) { - using DensityField = - mean_field::field::Field; + using DensityField = mean_field::field::Field; - const mean_field::quadrature::Query query = - DensityField::make_query( - mean_field::quadrature::QuadratureRole::diagnostic, - transformation.OrderW(), dynamic_orders, domain, - fem.has_mapping() ? mean_field::quadrature::MappingKind::general - : mean_field::quadrature::MappingKind::none - ); + const mean_field::quadrature::Query query = DensityField::make_query( + mean_field::quadrature::QuadratureRole::diagnostic, transformation.OrderW(), dynamic_orders, domain, + fem.has_mapping() ? mean_field::quadrature::MappingKind::general : mean_field::quadrature::MappingKind::none + ); - return *fem.quadratureFactory - ->get(query, transformation.GetGeometryType()) - .integration_rule; + return *fem.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule; } } // namespace @@ -45,15 +38,11 @@ namespace mean_field::analysis { double local_integral; mfem::Array elem_markers; populate_element_mask(fem.mesh.get(), domain, elem_markers); - const mfem::ElementTransformation &representative_transformation = - *fem.mesh->GetElementTransformation(0); + const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0); const mfem::IntegrationRule &integration_rule = - get_density_rule( - fem, representative_transformation, {}, domain - ); + get_density_rule(fem, representative_transformation, {}, domain); - if (fem.has_mapping() && - coord_space == mapping::COORDINATE_SPACE::PHYSICAL) { + if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) { mapping::MappedScalarCoefficient mapped_gf_c(*fem.mapping, gf_c); // ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM @@ -80,10 +69,7 @@ namespace mean_field::analysis { } double global_integral = 0.0; - MPI_Allreduce( - &local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM, - fem.mesh->GetComm() - ); + MPI_Allreduce(&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()); return global_integral; } @@ -99,12 +85,10 @@ namespace mean_field::analysis { for (int i = 0; i < fem.mesh->GetNE(); ++i) { if (fem.mesh->GetAttribute(i) == 3) continue; - mfem::ElementTransformation *trans = - fem.mesh->GetElementTransformation(i); - const mfem::IntegrationRule &ir = - get_density_rule( - fem, *trans, std::array{1}, utils::DOMAINS::STELLAR - ); + mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); + const mfem::IntegrationRule &ir = get_density_rule( + fem, *trans, std::array{1}, utils::DOMAINS::STELLAR + ); for (int j = 0; j < ir.GetNPoints(); ++j) { const mfem::IntegrationPoint &ip = ir.IntPoint(j); @@ -138,10 +122,7 @@ namespace mean_field::analysis { MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm); - MPI_Allreduce( - local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, - comm - ); + MPI_Allreduce(local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, comm); if (global_mass > 1e-18) { global_com /= global_mass; @@ -157,9 +138,7 @@ namespace mean_field::analysis { mfem::GridFunction &rho, const double target_mass ) { - if (const double current_mass = domain_integrate_grid_function( - fem, rho, utils::DOMAINS::STELLAR - ); + if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR); current_mass > 1e-15) rho *= (target_mass / current_mass); } @@ -168,16 +147,11 @@ namespace mean_field::analysis { const fem::FEM &fem, const mfem::GridFunction &rho ) { - auto s2_func = [](const mfem::Vector &x) { - return std::pow(x(0), 2) + std::pow(x(1), 2); - }; + auto s2_func = [](const mfem::Vector &x) { return std::pow(x(0), 2) + std::pow(x(1), 2); }; std::unique_ptr s2_coeff; if (fem.has_mapping()) { - s2_coeff = - std::make_unique( - *fem.mapping, s2_func - ); + s2_coeff = std::make_unique(*fem.mapping, s2_func); } else { s2_coeff = std::make_unique(s2_func); } @@ -186,23 +160,16 @@ namespace mean_field::analysis { mfem::ProductCoefficient I_integrand(rho_coeff, *s2_coeff); mfem::LinearForm I_lf(fem.densityFes.get()); - const mfem::ElementTransformation &representative_transformation = - *fem.mesh->GetElementTransformation(0); - const mfem::IntegrationRule &integration_rule = - get_density_rule( - fem, representative_transformation, std::array{2}, - utils::DOMAINS::STELLAR - ); - mfem::Array stellar_markers; - populate_element_mask( - fem.mesh.get(), utils::DOMAINS::STELLAR, stellar_markers + const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0); + const mfem::IntegrationRule &integration_rule = get_density_rule( + fem, representative_transformation, std::array{2}, utils::DOMAINS::STELLAR ); + mfem::Array stellar_markers; + populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, stellar_markers); double local_I = 0.0; if (fem.has_mapping()) { - mapping::MappedScalarCoefficient mapped_integrand( - *fem.mapping, I_integrand - ); + mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand); auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand); integrator->SetIntRule(&integration_rule); I_lf.AddDomainIntegrator(integrator, stellar_markers); @@ -217,9 +184,7 @@ namespace mean_field::analysis { } double global_I = 0.0; - MPI_Allreduce( - &local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm() - ); + MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()); return global_I; } @@ -229,13 +194,10 @@ namespace mean_field::analysis { const utils::DOMAINS domain ) { mfem::ParMesh &mesh = *fem.mesh; - const bool physical = - (coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL); + const bool physical = (coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL); if (physical && !fem.has_mapping()) { - MFEM_ABORT( - "Physical volume requested but no domain mapping is available." - ); + MFEM_ABORT("Physical volume requested but no domain mapping is available."); } double local_volume = 0.0; @@ -258,9 +220,7 @@ namespace mean_field::analysis { } mfem::ElementTransformation *T = mesh.GetElementTransformation(e); const mfem::IntegrationRule &ir = - get_density_rule( - fem, *T, {}, domain - ); + get_density_rule(fem, *T, {}, domain); for (int q = 0; q < ir.GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir.IntPoint(q); @@ -277,10 +237,7 @@ namespace mean_field::analysis { } double global_volume = 0.0; - MPI_Allreduce( - &local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM, - mesh.GetComm() - ); + MPI_Allreduce(&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM, mesh.GetComm()); return global_volume; } } // namespace mean_field::analysis diff --git a/libmeanfield/impl/fem.cpp b/libmeanfield/impl/fem.cpp index dbcd146..39aff4d 100644 --- a/libmeanfield/impl/fem.cpp +++ b/libmeanfield/impl/fem.cpp @@ -47,13 +47,9 @@ namespace mean_field::fem { int mpiSize = 1; MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); - const std::unique_ptr meshPartitioning( - fem.smesh.mesh->GeneratePartitioning(mpiSize, 1) - ); + const std::unique_ptr meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1)); - fem.mesh = std::make_unique( - MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1 - ); + fem.mesh = std::make_unique(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1); fem.mesh->EnsureNodes(); @@ -73,11 +69,9 @@ namespace mean_field::fem { throw std::runtime_error("Values for exterior coordinate not set."); } - const mfem::FiniteElementSpace &serialCoordinateSpace = - *fem.smesh.exterior_coordinate->space; + const mfem::FiniteElementSpace &serialCoordinateSpace = *fem.smesh.exterior_coordinate->space; - const mfem::GridFunction &serialCoordinate = - *fem.smesh.exterior_coordinate->values; + const mfem::GridFunction &serialCoordinate = *fem.smesh.exterior_coordinate->values; if (serialCoordinate.FESpace() != &serialCoordinateSpace) { throw std::runtime_error( @@ -94,9 +88,7 @@ namespace mean_field::fem { } if (serialCoordinateSpace.GetVDim() != 1) { - throw std::runtime_error( - "Exterior coordinate must be a scalar field." - ); + throw std::runtime_error("Exterior coordinate must be a scalar field."); } if (serialCoordinate.Size() != serialCoordinateSpace.GetVSize()) { @@ -106,50 +98,37 @@ namespace mean_field::fem { ); } - const int compactificationOrder = - serialCoordinateSpace.GetMaxElementOrder(); + const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder(); - const int dimension = fem.mesh->Dimension(); + const int dimension = fem.mesh->Dimension(); - fem.compactificationFec = std::make_unique( - compactificationOrder, dimension - ); + fem.compactificationFec = std::make_unique(compactificationOrder, dimension); - fem.compactificationFes = std::make_unique( - fem.mesh.get(), fem.compactificationFec.get() - ); + fem.compactificationFes = + std::make_unique(fem.mesh.get(), fem.compactificationFec.get()); - mfem::ParGridFunction distributedCoordinate( - fem.mesh.get(), &serialCoordinate, meshPartitioning.get() - ); + mfem::ParGridFunction distributedCoordinate(fem.mesh.get(), &serialCoordinate, meshPartitioning.get()); - if (distributedCoordinate.Size() != - fem.compactificationFes->GetVSize()) { + if (distributedCoordinate.Size() != fem.compactificationFes->GetVSize()) { throw std::runtime_error( "Distributed exterior coordinate does not match the " "constructed parallel finite-element space." ); } - fem.compactificationCoordinate = - std::make_unique( - fem.compactificationFes.get() - ); + fem.compactificationCoordinate = std::make_unique(fem.compactificationFes.get()); *fem.compactificationCoordinate = distributedCoordinate; - double localMinimum = std::numeric_limits::infinity(); + double localMinimum = std::numeric_limits::infinity(); - double localMaximum = -std::numeric_limits::infinity(); + double localMaximum = -std::numeric_limits::infinity(); - for (int index = 0; index < fem.compactificationCoordinate->Size(); - ++index) { + for (int index = 0; index < fem.compactificationCoordinate->Size(); ++index) { const double value = (*fem.compactificationCoordinate)(index); if (!std::isfinite(value)) { - throw std::runtime_error( - "Exterior coordinate contains a non-finite value." - ); + throw std::runtime_error("Exterior coordinate contains a non-finite value."); } localMinimum = std::min(localMinimum, value); @@ -160,20 +139,13 @@ namespace mean_field::fem { double globalMinimum = 0.0; double globalMaximum = 0.0; - MPI_Allreduce( - &localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, - MPI_COMM_WORLD - ); + MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD); - MPI_Allreduce( - &localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, - MPI_COMM_WORLD - ); + MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD); constexpr double coordinateTolerance = 1.0e-12; - if (globalMinimum < -coordinateTolerance || - globalMaximum > 1.0 + coordinateTolerance) { + if (globalMinimum < -coordinateTolerance || globalMaximum > 1.0 + coordinateTolerance) { throw std::runtime_error( "Exterior coordinate lies outside the expected " "interval [0, 1]." @@ -188,59 +160,45 @@ namespace mean_field::fem { // Gravity potential: scalar L2 // --------------------------------------------------------------------- - fem.gravityPotentialFec = - GravityField::make_fec(dimension); + fem.gravityPotentialFec = GravityField::make_fec(dimension); - fem.gravityPotentialFes = GravityField::make_fespace( - *fem.mesh, *fem.gravityPotentialFec - ); + fem.gravityPotentialFes = GravityField::make_fespace(*fem.mesh, *fem.gravityPotentialFec); // --------------------------------------------------------------------- // Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem. // --------------------------------------------------------------------- - fem.gravityFluxFec = GravityField::make_fec(dimension); + fem.gravityFluxFec = GravityField::make_fec(dimension); - fem.gravityFluxFes = GravityField::make_fespace( - *fem.mesh, *fem.gravityFluxFec - ); + fem.gravityFluxFes = GravityField::make_fespace(*fem.mesh, *fem.gravityFluxFec); // --------------------------------------------------------------------- // Displacement: vector H1. Ordering is encoded by field.mfem. // --------------------------------------------------------------------- - fem.displacementFec = - DisplacementField::make_fec(dimension); + fem.displacementFec = DisplacementField::make_fec(dimension); - fem.displacementFes = - DisplacementField::make_fespace( - *fem.mesh, *fem.displacementFec - ); + fem.displacementFes = DisplacementField::make_fespace(*fem.mesh, *fem.displacementFec); - fem.displacement = - std::make_unique(fem.displacementFes.get()); + fem.displacement = std::make_unique(fem.displacementFes.get()); - *fem.displacement = 0.0; + *fem.displacement = 0.0; // --------------------------------------------------------------------- // Density: scalar discontinuous L2 // --------------------------------------------------------------------- - fem.densityFec = DensityField::make_fec(dimension); + fem.densityFec = DensityField::make_fec(dimension); - fem.densityFes = DensityField::make_fespace( - *fem.mesh, *fem.densityFec - ); + fem.densityFes = DensityField::make_fespace(*fem.mesh, *fem.densityFec); // --------------------------------------------------------------------- // Specific enthalpy: scalar continuous H1 // --------------------------------------------------------------------- - fem.enthalpyFec = EnthalpyField::make_fec(dimension); + fem.enthalpyFec = EnthalpyField::make_fec(dimension); - fem.enthalpyFes = EnthalpyField::make_fespace( - *fem.mesh, *fem.enthalpyFec - ); + fem.enthalpyFes = EnthalpyField::make_fespace(*fem.mesh, *fem.enthalpyFec); // ===================================================================== // Section 4: Domain mapping @@ -248,21 +206,16 @@ namespace mean_field::fem { auto [stellarRadiusReference, infinityRadiusReference] = utils::discover_bounds(fem.mesh.get(), 3) - .or_else( - [](const boundary::BoundsError &) - -> std::expected< - boundary::Bounds, boundary::BoundsError> { - throw std::runtime_error( - "Unable to determine vacuum-domain reference " - "boundaries." - ); - } - ) + .or_else([](const boundary::BoundsError &) -> std::expected { + throw std::runtime_error( + "Unable to determine vacuum-domain reference " + "boundaries." + ); + }) .value(); - fem.mapping = std::make_unique( - *fem.displacement, stellarRadiusReference, infinityRadiusReference - ); + fem.mapping = + std::make_unique(*fem.displacement, stellarRadiusReference, infinityRadiusReference); // ===================================================================== // Section 5: Block offsets @@ -278,17 +231,14 @@ namespace mean_field::fem { fem.blockTrueOffsets[1] = fem.displacementFes->GetTrueVSize(); - fem.blockTrueOffsets[2] = - fem.blockTrueOffsets[1] + fem.densityFes->GetTrueVSize(); + fem.blockTrueOffsets[2] = fem.blockTrueOffsets[1] + fem.densityFes->GetTrueVSize(); fem.gravityBlockTrueOffsets.SetSize(3); fem.gravityBlockTrueOffsets[0] = 0; fem.gravityBlockTrueOffsets[1] = fem.gravityFluxFes->GetTrueVSize(); - fem.gravityBlockTrueOffsets[2] = - fem.gravityBlockTrueOffsets[1] + - fem.gravityPotentialFes->GetTrueVSize(); + fem.gravityBlockTrueOffsets[2] = fem.gravityBlockTrueOffsets[1] + fem.gravityPotentialFes->GetTrueVSize(); // ===================================================================== // Section 6: Multipole data @@ -306,10 +256,7 @@ namespace mean_field::fem { fem.essentialDisplacementTdofs.SetSize(0); - populate_element_mask( - fem.mesh.get(), utils::DOMAINS::STELLAR, - fem.gravityContext.stellar_mask - ); + populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravityContext.stellar_mask); const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max(); @@ -317,21 +264,18 @@ namespace mean_field::fem { fem.boundaryContext.stellar_bounds.SetSize(boundaryAttributeCount); - fem.boundaryContext.inf_bounds = 0; - fem.boundaryContext.stellar_bounds = 0; + fem.boundaryContext.inf_bounds = 0; + fem.boundaryContext.stellar_bounds = 0; - fem.boundaryContext.inf_bounds - [static_cast(boundary::Boundaries::INF_SURFACE) - 1] = 1; + fem.boundaryContext.inf_bounds[static_cast(boundary::Boundaries::INF_SURFACE) - 1] = 1; - fem.boundaryContext.stellar_bounds - [static_cast(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1; + fem.boundaryContext.stellar_bounds[static_cast(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1; // ===================================================================== // Section 8: Gravity solver context // ===================================================================== - fem.gravityContext.minres = - std::make_unique(fem.mesh->GetComm()); + fem.gravityContext.minres = std::make_unique(fem.mesh->GetComm()); fem.gravityContext.minres->SetRelTol(1.0e-12); fem.gravityContext.minres->SetAbsTol(1.0e-12); @@ -343,13 +287,9 @@ namespace mean_field::fem { fem.gravityContext.prec_Phi->SetPrintLevel(0); fem.gravityContext.block_prec = - std::make_unique( - fem.gravityBlockTrueOffsets - ); + std::make_unique(fem.gravityBlockTrueOffsets); - fem.gravityContext.minres->SetPreconditioner( - *fem.gravityContext.block_prec - ); + fem.gravityContext.minres->SetPreconditioner(*fem.gravityContext.block_prec); // ===================================================================== // Section 9: Vacuum true-DOF masks @@ -358,202 +298,115 @@ namespace mean_field::fem { { mfem::Array vacuumMask; - utils::populate_element_mask( - fem.mesh.get(), utils::DOMAINS::VACUUM, vacuumMask - ); + utils::populate_element_mask(fem.mesh.get(), utils::DOMAINS::VACUUM, vacuumMask); - utils::populate_domain_tdofs( - fem.displacementFes.get(), vacuumMask, - fem.vacuumDisplacementTdofs - ); + utils::populate_domain_tdofs(fem.displacementFes.get(), vacuumMask, fem.vacuumDisplacementTdofs); - utils::populate_domain_tdofs( - fem.densityFes.get(), vacuumMask, fem.vacuumDensityTdofs - ); + utils::populate_domain_tdofs(fem.densityFes.get(), vacuumMask, fem.vacuumDensityTdofs); - utils::populate_domain_tdofs( - fem.enthalpyFes.get(), vacuumMask, fem.vacuumEnthalpyTdofs - ); + utils::populate_domain_tdofs(fem.enthalpyFes.get(), vacuumMask, fem.vacuumEnthalpyTdofs); } // ===================================================================== // Section 10: Quadrature policy // ===================================================================== - const quadrature::QuadratureOptions &quadratureOptions = - args.quadrature; + const quadrature::QuadratureOptions &quadratureOptions = args.quadrature; - if (quadratureOptions.validation.reject_negative_boosts && - quadratureOptions.global_boost < 0) { - throw std::invalid_argument( - "Global quadrature boost cannot be negative." - ); + if (quadratureOptions.validation.reject_negative_boosts && quadratureOptions.global_boost < 0) { + throw std::invalid_argument("Global quadrature boost cannot be negative."); } - quadrature::RuleSet quadratureRuleSet = quadrature::make_rule_set( - quadratureOptions.mode, quadratureOptions.global_boost - ); + quadrature::RuleSet quadratureRuleSet = + quadrature::make_rule_set(quadratureOptions.mode, quadratureOptions.global_boost); if (quadratureOptions.fallback_fixed_order.has_value()) { if (*quadratureOptions.fallback_fixed_order < 0) { - throw std::invalid_argument( - "Fallback quadrature order cannot be negative." - ); + throw std::invalid_argument("Fallback quadrature order cannot be negative."); } - quadratureRuleSet.fallback.fixed_order = - quadratureOptions.fallback_fixed_order; + quadratureRuleSet.fallback.fixed_order = quadratureOptions.fallback_fixed_order; } - auto apply_quadrature_options = - [&quadratureOptions]( - quadrature::RuleControl &ruleControl, - const quadrature::QuadratureTermOptions &termOptions - ) { - if (termOptions.fixed_order.has_value() && - *termOptions.fixed_order < 0) { - throw std::invalid_argument( - "Fixed quadrature order cannot be negative." - ); - } + auto apply_quadrature_options = [&quadratureOptions]( + quadrature::RuleControl &ruleControl, + const quadrature::QuadratureTermOptions &termOptions + ) { + if (termOptions.fixed_order.has_value() && *termOptions.fixed_order < 0) { + throw std::invalid_argument("Fixed quadrature order cannot be negative."); + } - if (quadratureOptions.validation.reject_negative_boosts && - termOptions.additional_boost < 0) { - throw std::invalid_argument( - "Term quadrature boost cannot be negative." - ); - } + if (quadratureOptions.validation.reject_negative_boosts && termOptions.additional_boost < 0) { + throw std::invalid_argument("Term quadrature boost cannot be negative."); + } - ruleControl.boost += termOptions.additional_boost; + ruleControl.boost += termOptions.additional_boost; - if (termOptions.fixed_order.has_value()) { - ruleControl.fixed_order = termOptions.fixed_order; - } - }; + if (termOptions.fixed_order.has_value()) { + ruleControl.fixed_order = termOptions.fixed_order; + } + }; - apply_quadrature_options( - quadratureRuleSet.gravity_hdiv_mass, - quadratureOptions.gravity_hdiv_mass - ); + apply_quadrature_options(quadratureRuleSet.gravity_hdiv_mass, quadratureOptions.gravity_hdiv_mass); - apply_quadrature_options( - quadratureRuleSet.gravity_divergence, - quadratureOptions.gravity_divergence - ); + apply_quadrature_options(quadratureRuleSet.gravity_divergence, quadratureOptions.gravity_divergence); - apply_quadrature_options( - quadratureRuleSet.gravity_source, quadratureOptions.gravity_source - ); + apply_quadrature_options(quadratureRuleSet.gravity_source, quadratureOptions.gravity_source); - apply_quadrature_options( - quadratureRuleSet.gravity_boundary, - quadratureOptions.gravity_boundary - ); + apply_quadrature_options(quadratureRuleSet.gravity_force, quadratureOptions.gravity_force); - apply_quadrature_options( - quadratureRuleSet.centrifugal, quadratureOptions.centrifugal - ); + apply_quadrature_options(quadratureRuleSet.gravity_boundary, quadratureOptions.gravity_boundary); - apply_quadrature_options( - quadratureRuleSet.density_projection, - quadratureOptions.density_projection - ); + apply_quadrature_options(quadratureRuleSet.centrifugal, quadratureOptions.centrifugal); - apply_quadrature_options( - quadratureRuleSet.eos_closure, quadratureOptions.eos_closure - ); + apply_quadrature_options(quadratureRuleSet.density_projection, quadratureOptions.density_projection); - apply_quadrature_options( - quadratureRuleSet.hydrostatic_equilibrium, - quadratureOptions.hydrostatic_equilibrium - ); + apply_quadrature_options(quadratureRuleSet.eos_closure, quadratureOptions.eos_closure); - apply_quadrature_options( - quadratureRuleSet.isobaric_surface, - quadratureOptions.isobaric_surface - ); + apply_quadrature_options(quadratureRuleSet.hydrostatic_equilibrium, quadratureOptions.hydrostatic_equilibrium); - apply_quadrature_options( - quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension - ); + apply_quadrature_options(quadratureRuleSet.isobaric_surface, quadratureOptions.isobaric_surface); - apply_quadrature_options( - quadratureRuleSet.mass_conservation, - quadratureOptions.mass_conservation - ); + apply_quadrature_options(quadratureRuleSet.mesh_extension, quadratureOptions.mesh_extension); - apply_quadrature_options( - quadratureRuleSet.mass_normalization, - quadratureOptions.mass_normalization - ); + apply_quadrature_options(quadratureRuleSet.mass_conservation, quadratureOptions.mass_conservation); - apply_quadrature_options( - quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass - ); + apply_quadrature_options(quadratureRuleSet.mass_normalization, quadratureOptions.mass_normalization); - apply_quadrature_options( - quadratureRuleSet.quadrupole, quadratureOptions.quadrupole - ); + apply_quadrature_options(quadratureRuleSet.center_of_mass, quadratureOptions.center_of_mass); - apply_quadrature_options( - quadratureRuleSet.gravitational_energy, - quadratureOptions.gravitational_energy - ); + apply_quadrature_options(quadratureRuleSet.quadrupole, quadratureOptions.quadrupole); - apply_quadrature_options( - quadratureRuleSet.pressure_integral, - quadratureOptions.pressure_integral - ); + apply_quadrature_options(quadratureRuleSet.gravitational_energy, quadratureOptions.gravitational_energy); - apply_quadrature_options( - quadratureRuleSet.pressure_force, quadratureOptions.pressure_force - ); + apply_quadrature_options(quadratureRuleSet.pressure_integral, quadratureOptions.pressure_integral); - apply_quadrature_options( - quadratureRuleSet.virial, quadratureOptions.virial - ); + apply_quadrature_options(quadratureRuleSet.pressure_force, quadratureOptions.pressure_force); - apply_quadrature_options( - quadratureRuleSet.error_norm, quadratureOptions.error_norm - ); + apply_quadrature_options(quadratureRuleSet.virial, quadratureOptions.virial); - apply_quadrature_options( - quadratureRuleSet.roles.discretization, - quadratureOptions.roles.discretization - ); + apply_quadrature_options(quadratureRuleSet.error_norm, quadratureOptions.error_norm); - apply_quadrature_options( - quadratureRuleSet.roles.preconditioner, - quadratureOptions.roles.preconditioner - ); + apply_quadrature_options(quadratureRuleSet.roles.discretization, quadratureOptions.roles.discretization); - apply_quadrature_options( - quadratureRuleSet.roles.diagnostic, - quadratureOptions.roles.diagnostic - ); + apply_quadrature_options(quadratureRuleSet.roles.preconditioner, quadratureOptions.roles.preconditioner); - apply_quadrature_options( - quadratureRuleSet.roles.projection, - quadratureOptions.roles.projection - ); + apply_quadrature_options(quadratureRuleSet.roles.diagnostic, quadratureOptions.roles.diagnostic); - fem.quadratureFactory = std::make_unique( - quadrature::Policy(std::move(quadratureRuleSet)) - ); + apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection); + + fem.quadratureFactory = + std::make_unique(quadrature::Policy(std::move(quadratureRuleSet))); // ===================================================================== // Section 11: Stateless domain mapper // ===================================================================== - auto exteriorDomain = std::make_unique< - const mapping::compactification::KelvinCompactification>( - args.kelvin_options - ); + auto exteriorDomain = + std::make_unique(args.kelvin_options); fem.domainMapperStateless = - std::make_unique( - args.domain_mapper_options, std::move(exteriorDomain) - ); + std::make_unique(args.domain_mapper_options, std::move(exteriorDomain)); return fem; } diff --git a/libmeanfield/impl/integrators/advection.cpp b/libmeanfield/impl/integrators/advection.cpp index 4aee392..60c6430 100644 --- a/libmeanfield/impl/integrators/advection.cpp +++ b/libmeanfield/impl/integrators/advection.cpp @@ -4,8 +4,7 @@ module; module mean_field; namespace mean_field::integrators { - AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) - : m_map(map) { + AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) { } void AdvectionIntegrator::AssembleElementVector( @@ -39,8 +38,7 @@ namespace mean_field::integrators { mfem::Vector shape_v(dof_v), shape_rho(dof_rho); mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1); for (int q = 0; q < ir->GetNPoints(); q++) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -83,8 +81,7 @@ namespace mean_field::integrators { for (int i = 0; i < dof_v; ++i) { for (int c = 0; c < dim; ++c) { - r_v(i + c * dof_v) += - shape_v(i) * rho_val * adv_val(c) * weight; + r_v(i + c * dof_v) += shape_v(i) * rho_val * adv_val(c) * weight; } } } @@ -117,8 +114,7 @@ namespace mean_field::integrators { mfem::Vector shape_v(dof_v), shape_rho(dof_rho); mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1); for (int q = 0; q < ir->GetNPoints(); q++) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -171,8 +167,7 @@ namespace mean_field::integrators { double v_dot_grad_phi_j = 0.0; for (int k = 0; k < dim; ++k) { - v_dot_grad_phi_j += - v_val(k) * dshape_v_phys(j, k); + v_dot_grad_phi_j += v_val(k) * dshape_v_phys(j, k); } for (int d = 0; d < dim; ++d) { @@ -187,11 +182,9 @@ namespace mean_field::integrators { // \rho(\vec{v} \cdot \nabla \delta \vec{v}) // Only non-zero when the advected component // matches the test component - double termB = - (c == d) ? v_dot_grad_phi_j : 0.0; + double termB = (c == d) ? v_dot_grad_phi_j : 0.0; - (*dv_dv)(row, col) += shape_v(i) * rho_val * - (termA + termB) * weight; + (*dv_dv)(row, col) += shape_v(i) * rho_val * (termA + termB) * weight; } } } diff --git a/libmeanfield/impl/integrators/centrifugal.cpp b/libmeanfield/impl/integrators/centrifugal.cpp index f7de9fe..83fffa8 100644 --- a/libmeanfield/impl/integrators/centrifugal.cpp +++ b/libmeanfield/impl/integrators/centrifugal.cpp @@ -18,9 +18,7 @@ namespace mean_field::integrators { m_omega = omega; } - void CentrifugalForceIntegrator::SetIntegrationRule( - const mfem::IntegrationRule &ir - ) { + void CentrifugalForceIntegrator::SetIntegrationRule(const mfem::IntegrationRule &ir) { m_ir = &ir; } @@ -130,8 +128,7 @@ namespace mean_field::integrators { mfem::Vector x_phys(dim); mfem::Vector a(dim), b(dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -159,8 +156,7 @@ namespace mean_field::integrators { for (int c = 0; c < dim; ++c) { const int row = i + c * dof_v; for (int j = 0; j < dof_rho; ++j) { - (*dv_drho)(row, j) += - shape_v(i) * shape_rho(j) * b(c) * weight; + (*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * b(c) * weight; } } } diff --git a/libmeanfield/impl/integrators/coriolis.cpp b/libmeanfield/impl/integrators/coriolis.cpp index a1e9f94..579b549 100644 --- a/libmeanfield/impl/integrators/coriolis.cpp +++ b/libmeanfield/impl/integrators/coriolis.cpp @@ -49,8 +49,7 @@ namespace mean_field::integrators { } mfem::Vector shape_v(dof_v), shape_rho(dof_rho); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -78,8 +77,7 @@ namespace mean_field::integrators { for (int i = 0; i < dof_v; ++i) { for (int c = 0; c < dim; ++c) { - r_v(i + c * dof_v) += - shape_v(i) * rho_val * F_coriolis(c) * weight; + r_v(i + c * dof_v) += shape_v(i) * rho_val * F_coriolis(c) * weight; } } } @@ -111,8 +109,7 @@ namespace mean_field::integrators { *dv_drho = 0.0; mfem::Vector shape_v(dof_v), shape_rho(dof_rho); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -146,9 +143,7 @@ namespace mean_field::integrators { for (int d = 0; d < dim; ++d) { int col = j + d * dof_v; double coupling = m_omega_mat(c, d); - (*dv_dv)(row, col) += shape_v(i) * shape_v(j) * - 2.0 * rho_val * coupling * - weight; + (*dv_dv)(row, col) += shape_v(i) * shape_v(j) * 2.0 * rho_val * coupling * weight; } } } @@ -161,8 +156,7 @@ namespace mean_field::integrators { int row = i + c * dof_v; for (int j = 0; j < dof_rho; ++j) { int col = j; - (*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * - F_coriolis(c) * weight; + (*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * F_coriolis(c) * weight; } } } diff --git a/libmeanfield/impl/integrators/gravity.cpp b/libmeanfield/impl/integrators/gravity.cpp index e3b2783..29e9622 100644 --- a/libmeanfield/impl/integrators/gravity.cpp +++ b/libmeanfield/impl/integrators/gravity.cpp @@ -6,14 +6,10 @@ import :solver.fields; namespace { using namespace mean_field; - 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 displacement_block = - solver::block_index(solver::FieldBlock::displacement); + 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 displacement_block = solver::block_index(solver::FieldBlock::displacement); } // namespace namespace mean_field::integrators { @@ -25,20 +21,15 @@ namespace mean_field::integrators { m_jacobian_mode(jacobian_mode) { } - void GravityMomentumIntegrator::SetJacobianMode( - const GravityForceJacobianMode jacobian_mode - ) { + void GravityMomentumIntegrator::SetJacobianMode(const GravityForceJacobianMode jacobian_mode) { m_jacobian_mode = jacobian_mode; } - void GravityMomentumIntegrator::SetIntegrationRule( - const mfem::IntegrationRule &integration_rule - ) { + void GravityMomentumIntegrator::SetIntegrationRule(const mfem::IntegrationRule &integration_rule) { m_integration_rule = &integration_rule; } - GravityForceJacobianMode - GravityMomentumIntegrator::GetJacobianMode() const { + GravityForceJacobianMode GravityMomentumIntegrator::GetJacobianMode() const { return m_jacobian_mode; } @@ -53,27 +44,23 @@ namespace mean_field::integrators { } MFEM_VERIFY( - m_integration_rule, - "GravityForceIntegrator must be configured with an " - "integration rule before assembly." + m_integration_rule, "GravityForceIntegrator must be configured with an " + "integration rule before assembly." ); MFEM_VERIFY( - el.Size() > gravity_gradient_block, - "GravityForceIntegrator requires velocity, density, and " - "gravity-gradient finite elements." + el.Size() > gravity_gradient_block, "GravityForceIntegrator requires velocity, density, and " + "gravity-gradient finite elements." ); MFEM_VERIFY( - elfun.Size() > gravity_gradient_block, - "GravityForceIntegrator requires velocity, density, and " - "gravity-gradient element states." + elfun.Size() > gravity_gradient_block, "GravityForceIntegrator requires velocity, density, and " + "gravity-gradient element states." ); MFEM_VERIFY( elvec.Size() > velocity_block && elvec[velocity_block], "GravityForceIntegrator requires a velocity residual block." ); MFEM_VERIFY( - el[velocity_block] && el[density_block] && - el[gravity_gradient_block], + el[velocity_block] && el[density_block] && el[gravity_gradient_block], "GravityForceIntegrator received a null finite element." ); MFEM_VERIFY( @@ -81,25 +68,21 @@ namespace mean_field::integrators { "GravityForceIntegrator received a null element state." ); - const mfem::FiniteElement *velocity_element = el[velocity_block]; - const mfem::FiniteElement *density_element = el[density_block]; - const mfem::FiniteElement *gravity_gradient_element = - el[gravity_gradient_block]; + const mfem::FiniteElement *velocity_element = el[velocity_block]; + const mfem::FiniteElement *density_element = el[density_block]; + const mfem::FiniteElement *gravity_gradient_element = el[gravity_gradient_block]; - 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 dim = Tr.GetSpaceDim(); + 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 dim = Tr.GetSpaceDim(); - const mfem::Vector &density_dofs = *elfun[density_block]; - const mfem::Vector &gravity_gradient_dofs = - *elfun[gravity_gradient_block]; + const mfem::Vector &density_dofs = *elfun[density_block]; + const mfem::Vector &gravity_gradient_dofs = *elfun[gravity_gradient_block]; MFEM_VERIFY( - density_dofs.Size() == density_dofs_count, - "GravityForceIntegrator received an incorrectly sized density " - "state." + density_dofs.Size() == density_dofs_count, "GravityForceIntegrator received an incorrectly sized density " + "state." ); MFEM_VERIFY( gravity_gradient_dofs.Size() == gravity_gradient_dofs_count, @@ -123,40 +106,31 @@ namespace mean_field::integrators { *elvec[density_block] = 0.0; } - if (elvec.Size() > gravity_gradient_block && - elvec[gravity_gradient_block]) { + if (elvec.Size() > gravity_gradient_block && elvec[gravity_gradient_block]) { elvec[gravity_gradient_block]->SetSize(gravity_gradient_dofs_count); *elvec[gravity_gradient_block] = 0.0; } mfem::Vector velocity_shape(velocity_dofs_count); mfem::Vector density_shape(density_dofs_count); - mfem::DenseMatrix gravity_gradient_shape( - gravity_gradient_dofs_count, dim - ); + mfem::DenseMatrix gravity_gradient_shape(gravity_gradient_dofs_count, dim); mfem::Vector gravity_gradient_element_value(dim); mfem::Vector gravity_gradient_physical_value(dim); const mfem::IntegrationRule &integration_rule = *m_integration_rule; for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); Tr.SetIntPoint(&integration_point); - const mapping::VolumeQuadratureContext context = - m_map.GetQuadratureContext(Tr, integration_point); + const mapping::VolumeQuadratureContext context = m_map.GetQuadratureContext(Tr, integration_point); velocity_element->CalcShape(integration_point, velocity_shape); density_element->CalcShape(integration_point, density_shape); gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape); - gravity_gradient_shape.MultTranspose( - gravity_gradient_dofs, gravity_gradient_element_value - ); - context.J_inv.MultTranspose( - gravity_gradient_element_value, gravity_gradient_physical_value - ); + gravity_gradient_shape.MultTranspose(gravity_gradient_dofs, gravity_gradient_element_value); + context.J_inv.MultTranspose(gravity_gradient_element_value, gravity_gradient_physical_value); double density_value = 0.0; for (int i = 0; i < density_dofs_count; ++i) { @@ -166,9 +140,7 @@ namespace mean_field::integrators { for (int i = 0; i < velocity_dofs_count; ++i) { for (int component = 0; component < dim; ++component) { velocity_residual(i + component * velocity_dofs_count) += - velocity_shape(i) * density_value * - gravity_gradient_physical_value(component) * - context.weight; + velocity_shape(i) * density_value * gravity_gradient_physical_value(component) * context.weight; } } } @@ -185,23 +157,19 @@ namespace mean_field::integrators { } MFEM_VERIFY( - m_integration_rule, - "GravityForceIntegrator must be configured with an " - "integration rule before assembly." + m_integration_rule, "GravityForceIntegrator must be configured with an " + "integration rule before assembly." ); MFEM_VERIFY( - el.Size() > gravity_gradient_block, - "GravityForceIntegrator requires velocity, density, and " - "gravity-gradient finite elements." + el.Size() > gravity_gradient_block, "GravityForceIntegrator requires velocity, density, and " + "gravity-gradient finite elements." ); MFEM_VERIFY( - elfun.Size() > gravity_gradient_block, - "GravityForceIntegrator requires velocity, density, and " - "gravity-gradient element states." + elfun.Size() > gravity_gradient_block, "GravityForceIntegrator requires velocity, density, and " + "gravity-gradient element states." ); MFEM_VERIFY( - el[velocity_block] && el[density_block] && - el[gravity_gradient_block], + el[velocity_block] && el[density_block] && el[gravity_gradient_block], "GravityForceIntegrator received a null finite element." ); MFEM_VERIFY( @@ -226,25 +194,21 @@ namespace mean_field::integrators { ); } - const mfem::FiniteElement *velocity_element = el[velocity_block]; - const mfem::FiniteElement *density_element = el[density_block]; - const mfem::FiniteElement *gravity_gradient_element = - el[gravity_gradient_block]; + const mfem::FiniteElement *velocity_element = el[velocity_block]; + const mfem::FiniteElement *density_element = el[density_block]; + const mfem::FiniteElement *gravity_gradient_element = el[gravity_gradient_block]; - 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 dim = Tr.GetSpaceDim(); + 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 dim = Tr.GetSpaceDim(); - const mfem::Vector &density_dofs = *elfun[density_block]; - const mfem::Vector &gravity_gradient_dofs = - *elfun[gravity_gradient_block]; + const mfem::Vector &density_dofs = *elfun[density_block]; + const mfem::Vector &gravity_gradient_dofs = *elfun[gravity_gradient_block]; MFEM_VERIFY( - density_dofs.Size() == density_dofs_count, - "GravityForceIntegrator received an incorrectly sized density " - "state." + density_dofs.Size() == density_dofs_count, "GravityForceIntegrator received an incorrectly sized density " + "state." ); MFEM_VERIFY( gravity_gradient_dofs.Size() == gravity_gradient_dofs_count, @@ -253,11 +217,10 @@ namespace mean_field::integrators { "state." ); - mfem::DenseMatrix *dv_drho = elmats(velocity_block, density_block); - mfem::DenseMatrix *dv_dgrad_phi = - m_jacobian_mode == GravityForceJacobianMode::field_coupled - ? elmats(velocity_block, gravity_gradient_block) - : nullptr; + mfem::DenseMatrix *dv_drho = elmats(velocity_block, density_block); + mfem::DenseMatrix *dv_dgrad_phi = m_jacobian_mode == GravityForceJacobianMode::field_coupled + ? elmats(velocity_block, gravity_gradient_block) + : nullptr; if (!dv_drho && !dv_dgrad_phi) { return; @@ -265,9 +228,7 @@ namespace mean_field::integrators { mfem::Vector velocity_shape(velocity_dofs_count); mfem::Vector density_shape(density_dofs_count); - mfem::DenseMatrix gravity_gradient_shape( - gravity_gradient_dofs_count, dim - ); + mfem::DenseMatrix gravity_gradient_shape(gravity_gradient_dofs_count, dim); mfem::Vector gravity_gradient_element_value(dim); mfem::Vector gravity_gradient_physical_value(dim); mfem::Vector gravity_basis_element(dim); @@ -276,23 +237,17 @@ namespace mean_field::integrators { const mfem::IntegrationRule &integration_rule = *m_integration_rule; for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); Tr.SetIntPoint(&integration_point); - const mapping::VolumeQuadratureContext context = - m_map.GetQuadratureContext(Tr, integration_point); + const mapping::VolumeQuadratureContext context = m_map.GetQuadratureContext(Tr, integration_point); velocity_element->CalcShape(integration_point, velocity_shape); density_element->CalcShape(integration_point, density_shape); gravity_gradient_element->CalcVShape(Tr, gravity_gradient_shape); - gravity_gradient_shape.MultTranspose( - gravity_gradient_dofs, gravity_gradient_element_value - ); - context.J_inv.MultTranspose( - gravity_gradient_element_value, gravity_gradient_physical_value - ); + gravity_gradient_shape.MultTranspose(gravity_gradient_dofs, gravity_gradient_element_value); + context.J_inv.MultTranspose(gravity_gradient_element_value, gravity_gradient_physical_value); double density_value = 0.0; for (int i = 0; i < density_dofs_count; ++i) { @@ -305,10 +260,8 @@ namespace mean_field::integrators { const int row = i + component * velocity_dofs_count; for (int j = 0; j < density_dofs_count; ++j) { - (*dv_drho)(row, j) += - velocity_shape(i) * density_shape(j) * - gravity_gradient_physical_value(component) * - context.weight; + (*dv_drho)(row, j) += velocity_shape(i) * density_shape(j) * + gravity_gradient_physical_value(component) * context.weight; } } } @@ -317,21 +270,16 @@ namespace mean_field::integrators { if (dv_dgrad_phi) { for (int j = 0; j < gravity_gradient_dofs_count; ++j) { for (int component = 0; component < dim; ++component) { - gravity_basis_element(component) = - gravity_gradient_shape(j, component); + gravity_basis_element(component) = gravity_gradient_shape(j, component); } - context.J_inv.MultTranspose( - gravity_basis_element, gravity_basis_physical - ); + context.J_inv.MultTranspose(gravity_basis_element, gravity_basis_physical); for (int i = 0; i < velocity_dofs_count; ++i) { for (int component = 0; component < dim; ++component) { const int row = i + component * velocity_dofs_count; (*dv_dgrad_phi)(row, j) += - velocity_shape(i) * density_value * - gravity_basis_physical(component) * - context.weight; + velocity_shape(i) * density_value * gravity_basis_physical(component) * context.weight; } } } diff --git a/libmeanfield/impl/integrators/mass_continuity.cpp b/libmeanfield/impl/integrators/mass_continuity.cpp index cf79f39..2bbdb8e 100644 --- a/libmeanfield/impl/integrators/mass_continuity.cpp +++ b/libmeanfield/impl/integrators/mass_continuity.cpp @@ -4,10 +4,7 @@ module; module mean_field; namespace mean_field::integrators { - ContinuityVolumeIntegrator::ContinuityVolumeIntegrator( - const mapping::DomainMapper &map - ) - : m_map(map) { }; + ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper &map) : m_map(map) { }; void ContinuityVolumeIntegrator::AssembleElementVector( const mfem::Array &el, @@ -29,9 +26,8 @@ namespace mean_field::integrators { const mfem::Vector v_dofs = *elfun[0]; const mfem::Vector rho_dofs = *elfun[1]; - void *data_rho_before = - elvec[1] ? (void *)elvec[1]->GetData() : nullptr; - int size_rho_before = elvec[1] ? elvec[1]->Size() : -1; + void *data_rho_before = elvec[1] ? (void *)elvec[1]->GetData() : nullptr; + int size_rho_before = elvec[1] ? elvec[1]->Size() : -1; if (elvec[0]) { elvec[0]->SetSize(dof_v * dim); @@ -42,11 +38,9 @@ namespace mean_field::integrators { r_rho = 0.0; mfem::Vector shape_v(dof_v), shape_rho(dof_rho); - mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), - dshape_rho_phys(dof_rho, dim); + mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -113,11 +107,9 @@ namespace mean_field::integrators { *drho_drho = 0.0; mfem::Vector shape_v(dof_v), shape_rho(dof_rho); - mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), - dshape_rho_phys(dof_rho, dim); + mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -149,8 +141,7 @@ namespace mean_field::integrators { for (int j = 0; j < dof_v; ++j) { for (int d = 0; d < dim; ++d) { const int col = j + d * dof_v; - (*drho_dv)(i, col) -= dshape_rho_phys(i, d) * - rho_val * shape_v(j) * weight; + (*drho_dv)(i, col) -= dshape_rho_phys(i, d) * rho_val * shape_v(j) * weight; } } } @@ -163,18 +154,14 @@ namespace mean_field::integrators { grad_psi_dot_v += dshape_rho_phys(i, c) * v_val(c); } for (int j = 0; j < dof_rho; ++j) { - (*drho_drho)(i, j) -= - grad_psi_dot_v * shape_rho(j) * weight; + (*drho_drho)(i, j) -= grad_psi_dot_v * shape_rho(j) * weight; } } } } } - ContinuityFaceIntegrator::ContinuityFaceIntegrator( - const mapping::DomainMapper &map - ) - : m_map(map) { + ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper &map) : m_map(map) { } void ContinuityFaceIntegrator::AssembleFaceVector( @@ -204,10 +191,10 @@ namespace mean_field::integrators { } mfem::Vector &r_rho = *elvect[1]; r_rho.SetSize(dof_rho_minus + dof_rho_plus); - r_rho = 0.0; + r_rho = 0.0; - const int attr_minus = Tr.Elem1->Attribute; - const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; + const int attr_minus = Tr.Elem1->Attribute; + const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; constexpr int VACUUM_ATTR = 3; if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) { @@ -218,29 +205,21 @@ namespace mean_field::integrators { return; // Boundary face, } - const mfem::Vector &v_dofs = - *elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus - const mfem::Vector &rho_dofs = - *elfun[1]; // Size: dof_rho_minus + dof_rho_plus + const mfem::Vector &v_dofs = *elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus + const mfem::Vector &rho_dofs = *elfun[1]; // Size: dof_rho_minus + dof_rho_plus // Helpers to auto offset to the correct point in the dof array - auto rho_minus_dof = [&](const int i) { return rho_dofs(i); }; - auto rho_plus_dof = [&](const int i) { - return rho_dofs(i + dof_rho_minus); - }; - auto v_minus_dof = [&](const int k, const int c) { - return v_dofs(k + c * dof_v_minus); - }; + auto rho_minus_dof = [&](const int i) { return rho_dofs(i); }; + auto rho_plus_dof = [&](const int i) { return rho_dofs(i + dof_rho_minus); }; + auto v_minus_dof = [&](const int k, const int c) { return v_dofs(k + c * dof_v_minus); }; - const int p_v = fe_v_minus->GetOrder(); - const int p_rho = fe_rho_minus->GetOrder(); - const int int_order = 2 * std::max(p_v, p_rho) + 1; + const int p_v = fe_v_minus->GetOrder(); + const int p_rho = fe_rho_minus->GetOrder(); + const int int_order = 2 * std::max(p_v, p_rho) + 1; - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(Tr.GetGeometryType(), int_order); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order); - mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), - shape_rho_plus(dof_rho_plus); + mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &face_ip = ir->IntPoint(q); @@ -249,8 +228,7 @@ namespace mean_field::integrators { const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint(); const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint(); - auto [n_unit, ds, v_dot_n_scale] = - m_map.GetFaceQuadratureContext(Tr, face_ip); + auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip); fe_v_minus->CalcShape(ip_minus, shape_v_minus); fe_rho_minus->CalcShape(ip_minus, shape_rho_minus); @@ -280,7 +258,7 @@ namespace mean_field::integrators { // Upwind density // I use the convention that the flow is positive when moving from // minus to plus - const double rho_up = (u_n >= 0) ? rho_minus_val : rho_plus_val; + const double rho_up = (u_n >= 0) ? rho_minus_val : rho_plus_val; const double flux_weighted = u_n * rho_up * ds; @@ -314,11 +292,10 @@ namespace mean_field::integrators { const int dof_rho_plus = fe_rho_plus->GetDof(); const int dim = Tr.GetSpaceDim(); - const int N_v_total = dim * (dof_v_minus + dof_v_plus); - const int N_rho_total = dof_rho_minus + dof_rho_plus; + const int N_v_total = dim * (dof_v_minus + dof_v_plus); + const int N_rho_total = dof_rho_minus + dof_rho_plus; - auto size_and_zero_mat = [&](mfem::DenseMatrix *mat, const int r_size, - const int c_size) { + auto size_and_zero_mat = [&](mfem::DenseMatrix *mat, const int r_size, const int c_size) { if (mat) { mat->SetSize(r_size, c_size); *mat = 0.0; @@ -339,16 +316,13 @@ namespace mean_field::integrators { if (!drho_dv && !drho_drho) return; - const mfem::Vector &v_dofs = *elfun[0]; - const mfem::Vector &rho_dofs = *elfun[1]; + const mfem::Vector &v_dofs = *elfun[0]; + const mfem::Vector &rho_dofs = *elfun[1]; - const int int_order = - 2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1; - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(Tr.GetGeometryType(), int_order); + const int int_order = 2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1; + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order); - mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), - shape_rho_plus(dof_rho_plus); + mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &face_ip = ir->IntPoint(q); @@ -356,15 +330,13 @@ namespace mean_field::integrators { const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint(); const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint(); - auto [n_unit, ds, v_dot_n_scale] = - m_map.GetFaceQuadratureContext(Tr, face_ip); + auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip); fe_v_minus->CalcShape(ip_minus, shape_v_minus); fe_rho_minus->CalcShape(ip_minus, shape_rho_minus); fe_rho_plus->CalcShape(ip_plus, shape_rho_plus); - const double u_n = - compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim); + const double u_n = compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim); double rho_minus_val = 0.0; for (int i = 0; i < dof_rho_minus; ++i) { @@ -377,7 +349,7 @@ namespace mean_field::integrators { } const bool upwind_minus = (u_n >= 0.0); - const double rho_up = upwind_minus ? rho_minus_val : rho_plus_val; + const double rho_up = upwind_minus ? rho_minus_val : rho_plus_val; // (1, 1) if (drho_drho) { @@ -390,8 +362,7 @@ namespace mean_field::integrators { (*drho_drho)(i, ip) += shape_rho_minus(i) * col_w; } for (int j = 0; j < dof_rho_plus; ++j) { - (*drho_drho)(dof_rho_minus + j, ip) -= - shape_rho_plus(j) * col_w; + (*drho_drho)(dof_rho_minus + j, ip) -= shape_rho_plus(j) * col_w; } } } else { @@ -399,12 +370,10 @@ namespace mean_field::integrators { const double col_w = u_w * shape_rho_plus(jp); const int col_idx = dof_rho_minus + jp; for (int i = 0; i < dof_rho_minus; ++i) { - (*drho_drho)(i, col_idx) += - shape_rho_minus(i) * col_w; + (*drho_drho)(i, col_idx) += shape_rho_minus(i) * col_w; } for (int j = 0; j < dof_rho_plus; ++j) { - (*drho_drho)(dof_rho_minus + j, col_idx) -= - shape_rho_plus(j) * col_w; + (*drho_drho)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w; } } } @@ -418,12 +387,10 @@ namespace mean_field::integrators { const int col_idx = k + c * dof_v_minus; const double col_w = n_c_rho_w * shape_v_minus(k); for (int i = 0; i < dof_rho_minus; ++i) { - (*drho_dv)(i, col_idx) += - shape_rho_minus(i) * col_w; + (*drho_dv)(i, col_idx) += shape_rho_minus(i) * col_w; } for (int j = 0; j < dof_rho_plus; ++j) { - (*drho_dv)(dof_rho_minus + j, col_idx) -= - shape_rho_plus(j) * col_w; + (*drho_dv)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w; } } } @@ -431,12 +398,10 @@ namespace mean_field::integrators { } } - bool ContinuityFaceIntegrator::skip_face( - const mfem::FaceElementTransformations &Tr - ) { + bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) { constexpr int VACUUM_ATTR = 3; const int attr_minus = Tr.Elem1->Attribute; - const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; + const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1; if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) { return true; // No flux contribution for vacuum faces } diff --git a/libmeanfield/impl/integrators/viscosity.cpp b/libmeanfield/impl/integrators/viscosity.cpp index 0014a2f..a21d472 100644 --- a/libmeanfield/impl/integrators/viscosity.cpp +++ b/libmeanfield/impl/integrators/viscosity.cpp @@ -49,9 +49,7 @@ namespace mean_field::integrators { mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); - const mfem::IntegrationRule *ir = &mfem::IntRules.Get( - fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost - ); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost); for (int q = 0; q < ir->GetNPoints(); ++q) { @@ -127,8 +125,7 @@ namespace mean_field::integrators { mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); Tr.SetIntPoint(&ip); @@ -158,8 +155,7 @@ namespace mean_field::integrators { val += dshape_v_phys(i, d) * dshape_v_phys(n, c); - val -= (2.0 / 3.0) * dshape_v_phys(i, c) * - dshape_v_phys(n, d); + val -= (2.0 / 3.0) * dshape_v_phys(i, c) * dshape_v_phys(n, d); (*dv_dv)(row, col) += mu_w * val; } } diff --git a/libmeanfield/impl/mapping/coefficients.cpp b/libmeanfield/impl/mapping/coefficients.cpp index ab5cc0c..a609584 100644 --- a/libmeanfield/impl/mapping/coefficients.cpp +++ b/libmeanfield/impl/mapping/coefficients.cpp @@ -168,10 +168,7 @@ namespace mean_field::mapping { const double map_determinant = map_jacobian.Det(); - MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian determinant." - ); + MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); mfem::MultAtB(map_jacobian, map_jacobian, matrix); matrix *= 1.0 / std::abs(map_determinant); diff --git a/libmeanfield/impl/mapping/compactification/kelvin.cpp b/libmeanfield/impl/mapping/compactification/kelvin.cpp index 822ea6c..e7f39ea 100644 --- a/libmeanfield/impl/mapping/compactification/kelvin.cpp +++ b/libmeanfield/impl/mapping/compactification/kelvin.cpp @@ -27,26 +27,17 @@ namespace { } // namespace namespace mean_field::mapping::compactification { - KelvinCompactification::KelvinCompactification( - options::KelvinCompactificationOptions options - ) + KelvinCompactification::KelvinCompactification(options::KelvinCompactificationOptions options) : m_options(options) { - if (!std::isfinite(m_options.r_star_ref) || - !std::isfinite(m_options.r_inf_ref)) { - throw std::invalid_argument( - "Kelvin compactification radii must be finite." - ); + if (!std::isfinite(m_options.r_star_ref) || !std::isfinite(m_options.r_inf_ref)) { + throw std::invalid_argument("Kelvin compactification radii must be finite."); } - if (m_options.r_star_ref <= 0.0 || - m_options.r_inf_ref <= m_options.r_star_ref) { - throw std::invalid_argument( - "Kelvin compactification requires 0 < r_star_ref < r_inf_ref." - ); + if (m_options.r_star_ref <= 0.0 || m_options.r_inf_ref <= m_options.r_star_ref) { + throw std::invalid_argument("Kelvin compactification requires 0 < r_star_ref < r_inf_ref."); } - if (!std::isfinite(m_options.coordinate_tolerance) || - m_options.coordinate_tolerance < 0.0 || + if (!std::isfinite(m_options.coordinate_tolerance) || m_options.coordinate_tolerance < 0.0 || m_options.coordinate_tolerance >= 1.0) { throw std::invalid_argument( "Kelvin compactification coordinate tolerance must be finite " @@ -65,8 +56,7 @@ namespace mean_field::mapping::compactification { const double tolerance = m_options.coordinate_tolerance; - if (compactification_coordinate < -tolerance || - compactification_coordinate > 1.0 + tolerance) { + if (compactification_coordinate < -tolerance || compactification_coordinate > 1.0 + tolerance) { return MappingStatus::outside_reference_domain; } @@ -78,26 +68,22 @@ namespace mean_field::mapping::compactification { return MappingStatus::at_compactified_infinity; } - const double radial_extent = m_options.r_inf_ref - m_options.r_star_ref; - const double computational_radius = - m_options.r_star_ref + coordinate * radial_extent; + const double radial_extent = m_options.r_inf_ref - m_options.r_star_ref; + const double computational_radius = m_options.r_star_ref + coordinate * radial_extent; - if (!std::isfinite(computational_radius) || - computational_radius <= 0.0) { + if (!std::isfinite(computational_radius) || computational_radius <= 0.0) { return MappingStatus::invalid_reference_radius; } const double one_minus_coordinate = 1.0 - coordinate; - const double denominator = computational_radius * one_minus_coordinate; + const double denominator = computational_radius * one_minus_coordinate; if (!std::isfinite(denominator) || denominator <= 0.0) { return MappingStatus::non_finite_result; } - const double scale = m_options.r_star_ref / denominator; - const double scale_derivative = - scale * - (1.0 / one_minus_coordinate - radial_extent / computational_radius); + const double scale = m_options.r_star_ref / denominator; + const double scale_derivative = scale * (1.0 / one_minus_coordinate - radial_extent / computational_radius); if (!std::isfinite(scale) || !std::isfinite(scale_derivative)) { return MappingStatus::non_finite_result; @@ -122,21 +108,18 @@ namespace mean_field::mapping::compactification { return MappingStatus::invalid_dimension; } - if (input.displacement_jacobian.Height() != dimension || - input.displacement_jacobian.Width() != dimension) { + if (input.displacement_jacobian.Height() != dimension || input.displacement_jacobian.Width() != dimension) { return MappingStatus::invalid_dimension; } - if (!vector_is_finite(input.reference_position) || - !vector_is_finite(input.displaced_position) || + if (!vector_is_finite(input.reference_position) || !vector_is_finite(input.displaced_position) || !vector_is_finite(input.compactification_coordinate_gradient) || !matrix_is_finite(input.displacement_jacobian)) { return MappingStatus::non_finite_input; } RadialFactors factors; - const MappingStatus factor_status = - ComputeRadialFactors(input.compactification_coordinate, factors); + const MappingStatus factor_status = ComputeRadialFactors(input.compactification_coordinate, factors); if (factor_status != MappingStatus::valid) return factor_status; @@ -144,21 +127,16 @@ namespace mean_field::mapping::compactification { result.mapping_jacobian.SetSize(dimension, dimension); for (int i = 0; i < dimension; ++i) { - result.physical_position(i) = - factors.scale * input.displaced_position(i); + result.physical_position(i) = factors.scale * input.displaced_position(i); for (int j = 0; j < dimension; ++j) { - const double scale_gradient = - factors.scale_derivative * - input.compactification_coordinate_gradient(j); + const double scale_gradient = factors.scale_derivative * input.compactification_coordinate_gradient(j); result.mapping_jacobian(i, j) = - factors.scale * input.displacement_jacobian(i, j) + - input.displaced_position(i) * scale_gradient; + factors.scale * input.displacement_jacobian(i, j) + input.displaced_position(i) * scale_gradient; } } - if (!vector_is_finite(result.physical_position) || - !matrix_is_finite(result.mapping_jacobian)) { + if (!vector_is_finite(result.physical_position) || !matrix_is_finite(result.mapping_jacobian)) { return MappingStatus::non_finite_result; } @@ -185,13 +163,11 @@ namespace mean_field::mapping::compactification { return MappingStatus::invalid_dimension; } - if (input.displacement_jacobian.Height() != dimension || - input.displacement_jacobian.Width() != dimension) { + if (input.displacement_jacobian.Height() != dimension || input.displacement_jacobian.Width() != dimension) { return MappingStatus::invalid_dimension; } - if (result.physical_position.Size() != dimension || - result.mapping_jacobian.Height() != dimension || + if (result.physical_position.Size() != dimension || result.mapping_jacobian.Height() != dimension || result.mapping_jacobian.Width() != dimension) { return MappingStatus::invalid_dimension; } @@ -202,23 +178,20 @@ namespace mean_field::mapping::compactification { return MappingStatus::invalid_dimension; } - if (!vector_is_finite(input.reference_position) || - !vector_is_finite(input.displaced_position) || + if (!vector_is_finite(input.reference_position) || !vector_is_finite(input.displaced_position) || !vector_is_finite(input.compactification_coordinate_gradient) || !matrix_is_finite(input.displacement_jacobian)) { return MappingStatus::non_finite_input; } - if (!vector_is_finite(result.physical_position) || - !matrix_is_finite(result.mapping_jacobian) || + if (!vector_is_finite(result.physical_position) || !matrix_is_finite(result.mapping_jacobian) || !vector_is_finite(direction.displaced_position_variation) || !matrix_is_finite(direction.displacement_jacobian_variation)) { return MappingStatus::non_finite_input; } RadialFactors factors; - const MappingStatus factor_status = - ComputeRadialFactors(input.compactification_coordinate, factors); + const MappingStatus factor_status = ComputeRadialFactors(input.compactification_coordinate, factors); if (factor_status != MappingStatus::valid) return factor_status; @@ -226,16 +199,12 @@ namespace mean_field::mapping::compactification { variation.mapping_jacobian_variation.SetSize(dimension, dimension); for (int i = 0; i < dimension; ++i) { - variation.physical_position_variation(i) = - factors.scale * direction.displaced_position_variation(i); + variation.physical_position_variation(i) = factors.scale * direction.displaced_position_variation(i); for (int j = 0; j < dimension; ++j) { - const double scale_gradient = - factors.scale_derivative * - input.compactification_coordinate_gradient(j); + const double scale_gradient = factors.scale_derivative * input.compactification_coordinate_gradient(j); variation.mapping_jacobian_variation(i, j) = - factors.scale * - direction.displacement_jacobian_variation(i, j) + + factors.scale * direction.displacement_jacobian_variation(i, j) + direction.displaced_position_variation(i) * scale_gradient; } } diff --git a/libmeanfield/impl/mapping/domain_mapper.cpp b/libmeanfield/impl/mapping/domain_mapper.cpp index 7fb44f6..a85bec1 100644 --- a/libmeanfield/impl/mapping/domain_mapper.cpp +++ b/libmeanfield/impl/mapping/domain_mapper.cpp @@ -15,10 +15,7 @@ namespace { domain_mapper.ComputeJacobian(transformation, map_jacobian); const double map_determinant = map_jacobian.Det(); - MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian determinant." - ); + MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); return map_determinant; } } // namespace @@ -32,8 +29,7 @@ namespace mean_field::mapping { m_r_star_ref(r_star_ref), m_r_inf_ref(r_inf_ref) { InitAllScratchSpaces(); - CalcIsIdentity() ? m_displacement_is_identity = true - : m_displacement_is_identity = false; + CalcIsIdentity() ? m_displacement_is_identity = true : m_displacement_is_identity = false; } DomainMapper::DomainMapper( @@ -46,8 +42,7 @@ namespace mean_field::mapping { m_r_star_ref(r_star_ref), m_r_inf_ref(r_inf_ref) { InitAllScratchSpaces(); - CalcIsIdentity() ? m_displacement_is_identity = true - : m_displacement_is_identity = false; + CalcIsIdentity() ? m_displacement_is_identity = true : m_displacement_is_identity = false; } bool DomainMapper::is_vacuum(const mfem::ElementTransformation &T) const { @@ -76,14 +71,12 @@ namespace mean_field::mapping { m_d = &d; InvalidateCache(); - CalcIsIdentity() ? m_displacement_is_identity = true - : m_displacement_is_identity = false; + CalcIsIdentity() ? m_displacement_is_identity = true : m_displacement_is_identity = false; } bool DomainMapper::HasCompactification() const noexcept { - return std::isfinite(m_r_star_ref) && std::isfinite(m_r_inf_ref) && - m_r_star_ref > 0.0 && m_r_inf_ref > m_r_star_ref && - m_xi_clamp > 0.0 && m_xi_clamp < 1.0; + return std::isfinite(m_r_star_ref) && std::isfinite(m_r_inf_ref) && m_r_star_ref > 0.0 && + m_r_inf_ref > m_r_star_ref && m_xi_clamp > 0.0 && m_xi_clamp < 1.0; } bool DomainMapper::HasDisplacementField() const noexcept { @@ -95,10 +88,9 @@ namespace mean_field::mapping { return true; } - const int local_identity = m_d->Normlinf() == 0.0 ? 1 : 0; + const int local_identity = m_d->Normlinf() == 0.0 ? 1 : 0; - const auto *parallel_displacement = - dynamic_cast(m_d); + const auto *parallel_displacement = dynamic_cast(m_d); if (parallel_displacement == nullptr) { return local_identity == 1; @@ -106,8 +98,7 @@ namespace mean_field::mapping { int global_identity = 0; MPI_Allreduce( - &local_identity, &global_identity, 1, MPI_INT, MPI_MIN, - parallel_displacement->ParFESpace()->GetComm() + &local_identity, &global_identity, 1, MPI_INT, MPI_MIN, parallel_displacement->ParFESpace()->GetComm() ); return global_identity == 1; @@ -116,8 +107,7 @@ namespace mean_field::mapping { void DomainMapper::ResetDisplacement() { m_d = nullptr; InvalidateCache(); - CalcIsIdentity() ? m_displacement_is_identity = true - : m_displacement_is_identity = false; + CalcIsIdentity() ? m_displacement_is_identity = true : m_displacement_is_identity = false; } void DomainMapper::ComputeJacobian( @@ -223,11 +213,7 @@ namespace mean_field::mapping { mfem::Vector n_unit(dim); n_unit = n_raw; n_unit /= n_raw_mag; - return FaceQuadratureContext{ - .normal = n_unit, - .ds = ip.weight * n_raw_mag, - .v_dot_n_scale = 1.0 - }; + return FaceQuadratureContext{.normal = n_unit, .ds = ip.weight * n_raw_mag, .v_dot_n_scale = 1.0}; } // Nanson's Formula @@ -253,9 +239,7 @@ namespace mean_field::mapping { const double n_raw_mag = n_raw.Norml2(); return FaceQuadratureContext{ - .normal = n_unit, - .ds = ip.weight * n_raw_mag, - .v_dot_n_scale = n_phys_mag / n_raw_mag + .normal = n_unit, .ds = ip.weight * n_raw_mag, .v_dot_n_scale = n_phys_mag / n_raw_mag }; } @@ -297,15 +281,11 @@ namespace mean_field::mapping { const mfem::Vector &reference_flux, mfem::Vector &physical_flux ) const { - MFEM_VERIFY( - reference_flux.Size() == m_dim, - "The reference H(div) flux has the wrong dimension." - ); + MFEM_VERIFY(reference_flux.Size() == m_dim, "The reference H(div) flux has the wrong dimension."); mfem::DenseMatrix map_jacobian(m_dim, m_dim); - const double map_determinant = get_positive_map_jacobian( - *this, transformation, integration_point, map_jacobian - ); + const double map_determinant = + get_positive_map_jacobian(*this, transformation, integration_point, map_jacobian); mfem::Vector mapped_flux(m_dim); map_jacobian.Mult(reference_flux, mapped_flux); @@ -320,15 +300,11 @@ namespace mean_field::mapping { const mfem::Vector &physical_flux, mfem::Vector &reference_flux ) const { - MFEM_VERIFY( - physical_flux.Size() == m_dim, - "The physical flux has the wrong dimension." - ); + MFEM_VERIFY(physical_flux.Size() == m_dim, "The physical flux has the wrong dimension."); mfem::DenseMatrix map_jacobian(m_dim, m_dim); - const double map_determinant = get_positive_map_jacobian( - *this, transformation, integration_point, map_jacobian - ); + const double map_determinant = + get_positive_map_jacobian(*this, transformation, integration_point, map_jacobian); mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim); mfem::CalcInverse(map_jacobian, inverse_map_jacobian); @@ -346,15 +322,10 @@ namespace mean_field::mapping { const mfem::Vector &reference_gradient, mfem::Vector &physical_gradient ) const { - MFEM_VERIFY( - reference_gradient.Size() == m_dim, - "The reference gradient has the wrong dimension." - ); + MFEM_VERIFY(reference_gradient.Size() == m_dim, "The reference gradient has the wrong dimension."); mfem::DenseMatrix map_jacobian(m_dim, m_dim); - get_positive_map_jacobian( - *this, transformation, integration_point, map_jacobian - ); + get_positive_map_jacobian(*this, transformation, integration_point, map_jacobian); mfem::DenseMatrix inverse_map_jacobian(m_dim, m_dim); mfem::CalcInverse(map_jacobian, inverse_map_jacobian); @@ -382,8 +353,7 @@ namespace mean_field::mapping { } double DomainMapper::GetCacheHitRate() const { - return (static_cast(m_cache_hits)) / - static_cast(m_cache_misses + m_cache_hits); + return (static_cast(m_cache_hits)) / static_cast(m_cache_misses + m_cache_hits); } void DomainMapper::ResetCacheStats() const { @@ -404,9 +374,9 @@ namespace mean_field::mapping { const mfem::Vector &x_ref, mfem::Vector &x_phys ) const { - const double r_ref = x_ref.Norml2(); - double xi = (r_ref - m_r_star_ref) / (m_r_inf_ref - m_r_star_ref); - xi = std::clamp(xi, 0.0, m_xi_clamp); + const double r_ref = x_ref.Norml2(); + double xi = (r_ref - m_r_star_ref) / (m_r_inf_ref - m_r_star_ref); + xi = std::clamp(xi, 0.0, m_xi_clamp); const double factor = m_r_star_ref / (r_ref * (1 - xi)); x_phys *= factor; } @@ -428,8 +398,7 @@ namespace mean_field::mapping { const double k = m_r_star_ref / (r_ref * denom); const double dk_dr = - m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) - - (1.0 / (r_ref * r_ref * denom))); + m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) - (1.0 / (r_ref * r_ref * denom))); J.SetSize(m_dim, m_dim); const double outer_factor = dk_dr / r_ref; @@ -445,14 +414,11 @@ namespace mean_field::mapping { m_cached_elem_id = -1; } - void DomainMapper::UpdateElementCache( - const mfem::ElementTransformation &T - ) const { + void DomainMapper::UpdateElementCache(const mfem::ElementTransformation &T) const { if (!HasDisplacementField()) return; - if (T.ElementNo != m_cached_elem_id || - T.ElementType != m_cached_elem_type) { + if (T.ElementNo != m_cached_elem_id || T.ElementType != m_cached_elem_type) { m_cache_misses++; m_cached_elem_id = T.ElementNo; m_cached_elem_type = T.ElementType; diff --git a/libmeanfield/impl/mapping/domain_mapper_new.cpp b/libmeanfield/impl/mapping/domain_mapper_new.cpp index c639ed7..60c312b 100644 --- a/libmeanfield/impl/mapping/domain_mapper_new.cpp +++ b/libmeanfield/impl/mapping/domain_mapper_new.cpp @@ -39,9 +39,7 @@ namespace mean_field::mapping { : m_element(&element), m_dofs(dofs) { if (element.GetRangeType() != mfem::FiniteElement::SCALAR) { - throw std::invalid_argument( - "Compactification coordinate requires a scalar finite element." - ); + throw std::invalid_argument("Compactification coordinate requires a scalar finite element."); } if (element.GetMapType() != mfem::FiniteElement::VALUE) { @@ -75,8 +73,7 @@ namespace mean_field::mapping { } } - const mfem::FiniteElement & - ElementCompactificationData::GetElement() const noexcept { + const mfem::FiniteElement &ElementCompactificationData::GetElement() const noexcept { return *m_element; } @@ -102,8 +99,7 @@ namespace mean_field::mapping { "The displacement element must have at least one degree of " "freedom." ); - if (displacement_dofs.Size() <= 0 || - displacement_dofs.Size() % dof_count != 0) { + if (displacement_dofs.Size() <= 0 || displacement_dofs.Size() % dof_count != 0) { throw std::invalid_argument( "The displacement vector size must be a positive multiple of " "the " @@ -117,31 +113,25 @@ namespace mean_field::mapping { if (ordering == mfem::Ordering::byNODES) { for (int component = 0; component < m_dimension; ++component) { for (int i = 0; i < dof_count; ++i) { - m_dof_matrix(i, component) = - displacement_dofs(i + component * dof_count); + m_dof_matrix(i, component) = displacement_dofs(i + component * dof_count); } } } else if (ordering == mfem::Ordering::byVDIM) { for (int i = 0; i < dof_count; ++i) { for (int component = 0; component < m_dimension; ++component) { - m_dof_matrix(i, component) = - displacement_dofs(component + i * m_dimension); + m_dof_matrix(i, component) = displacement_dofs(component + i * m_dimension); } } } else { - throw std::invalid_argument( - "Unsupported MFEM displacement ordering." - ); + throw std::invalid_argument("Unsupported MFEM displacement ordering."); } } - const mfem::FiniteElement & - ElementDisplacementData::GetElement() const noexcept { + const mfem::FiniteElement &ElementDisplacementData::GetElement() const noexcept { return *m_element; } - const mfem::DenseMatrix & - ElementDisplacementData::GetDofMatrix() const noexcept { + const mfem::DenseMatrix &ElementDisplacementData::GetDofMatrix() const noexcept { return m_dof_matrix; } @@ -161,9 +151,7 @@ namespace mean_field::mapping { const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs ) { - return ElementDisplacementData( - element, displacement_dofs, mfem::Ordering::byNODES - ); + return ElementDisplacementData(element, displacement_dofs, mfem::Ordering::byNODES); } DomainMapperStateless::Workspace::Workspace(const int dimension) { @@ -172,9 +160,7 @@ namespace mean_field::mapping { void DomainMapperStateless::Workspace::SetDimension(const int dimension) { if (dimension <= 0) { - throw std::invalid_argument( - "Domain mapping workspace dimension must be positive." - ); + throw std::invalid_argument("Domain mapping workspace dimension must be positive."); } m_dimension = dimension; @@ -197,9 +183,7 @@ namespace mean_field::mapping { m_exterior_result.mapping_jacobian.SetSize(dimension, dimension); m_exterior_variation.physical_position_variation.SetSize(dimension); - m_exterior_variation.mapping_jacobian_variation.SetSize( - dimension, dimension - ); + m_exterior_variation.mapping_jacobian_variation.SetSize(dimension, dimension); } int DomainMapperStateless::Workspace::GetDimension() const noexcept { @@ -213,22 +197,15 @@ namespace mean_field::mapping { : m_options(options), m_exterior_map(std::move(exterior_map)) { if (m_options.dimension <= 0) - throw std::invalid_argument( - "The domain-mapping dimension must be positive." - ); + throw std::invalid_argument("The domain-mapping dimension must be positive."); if (m_options.vacuum_element_attribute <= 0) - throw std::invalid_argument( - "The vacuum element attribute must be positive." - ); + throw std::invalid_argument("The vacuum element attribute must be positive."); if (!m_exterior_map) - throw std::invalid_argument( - "DomainMapperStateless requires an exterior-domain mapping." - ); + throw std::invalid_argument("DomainMapperStateless requires an exterior-domain mapping."); } - bool DomainMapperStateless::IsCompactifiedElement( - const mfem::ElementTransformation &transformation - ) const noexcept { + bool + DomainMapperStateless::IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept { return transformation.Attribute == m_options.vacuum_element_attribute; } @@ -240,17 +217,13 @@ namespace mean_field::mapping { return m_options.vacuum_element_attribute; } - const compactification::ExteriorDomainMap & - DomainMapperStateless::GetExteriorMap() const noexcept { + const compactification::ExteriorDomainMap &DomainMapperStateless::GetExteriorMap() const noexcept { return *m_exterior_map; } - void DomainMapperStateless::ValidateElementData( - const ElementMappingData &element_data - ) const { - const ElementDisplacementData &displacement = element_data.displacement; - const ElementCompactificationData &compactification = - element_data.compactification; + void DomainMapperStateless::ValidateElementData(const ElementMappingData &element_data) const { + const ElementDisplacementData &displacement = element_data.displacement; + const ElementCompactificationData &compactification = element_data.compactification; if (displacement.GetDimension() != m_options.dimension) { throw std::invalid_argument( @@ -275,8 +248,7 @@ namespace mean_field::mapping { ); } - if (displacement.GetElement().GetGeomType() != - compactification.GetElement().GetGeomType()) { + if (displacement.GetElement().GetGeomType() != compactification.GetElement().GetGeomType()) { throw std::invalid_argument( "Displacement and compactification finite elements have " "different " @@ -284,31 +256,25 @@ namespace mean_field::mapping { ); } - if (compactification.GetElement().GetRangeType() != - mfem::FiniteElement::SCALAR) { - throw std::invalid_argument( - "Compactification coordinate requires a scalar finite element." - ); + if (compactification.GetElement().GetRangeType() != mfem::FiniteElement::SCALAR) { + throw std::invalid_argument("Compactification coordinate requires a scalar finite element."); } - if (compactification.GetElement().GetMapType() != - mfem::FiniteElement::VALUE) { + if (compactification.GetElement().GetMapType() != mfem::FiniteElement::VALUE) { throw std::invalid_argument( "Compactification coordinate requires a value-mapped finite " "element." ); } - if (compactification.GetElement().GetDerivType() != - mfem::FiniteElement::GRAD) { + if (compactification.GetElement().GetDerivType() != mfem::FiniteElement::GRAD) { throw std::invalid_argument( "Compactification coordinate finite element does not provide a " "gradient." ); } - if (compactification.GetDofCount() != - compactification.GetElement().GetDof()) { + if (compactification.GetDofCount() != compactification.GetElement().GetDof()) { throw std::invalid_argument( "Compactification coordinate DOF count does not match its " "finite " @@ -328,8 +294,7 @@ namespace mean_field::mapping { const mfem::Vector &dofs = compactification.GetDofs(); const int dof_count = element.GetDof(); - if (workspace.GetDimension() != m_options.dimension || - transformation.GetSpaceDim() != m_options.dimension || + if (workspace.GetDimension() != m_options.dimension || transformation.GetSpaceDim() != m_options.dimension || element.GetDim() != m_options.dimension) { return MappingStatus::invalid_dimension; } @@ -346,22 +311,14 @@ namespace mean_field::mapping { transformation.SetIntPoint(&integration_point); workspace.m_compactification_shape.SetSize(dof_count); - workspace.m_compactification_dshape.SetSize( - dof_count, m_options.dimension - ); + workspace.m_compactification_dshape.SetSize(dof_count, m_options.dimension); - element.CalcShape( - integration_point, workspace.m_compactification_shape - ); - element.CalcPhysDShape( - transformation, workspace.m_compactification_dshape - ); + element.CalcShape(integration_point, workspace.m_compactification_shape); + element.CalcPhysDShape(transformation, workspace.m_compactification_dshape); point_data.coordinate = dofs * workspace.m_compactification_shape; point_data.coordinate_gradient.SetSize(m_options.dimension); - workspace.m_compactification_dshape.MultTranspose( - dofs, point_data.coordinate_gradient - ); + workspace.m_compactification_dshape.MultTranspose(dofs, point_data.coordinate_gradient); if (!std::isfinite(point_data.coordinate)) { return MappingStatus::non_finite_result; @@ -411,15 +368,10 @@ namespace mean_field::mapping { ValidateElementData(element_data); if (workspace.GetDimension() != m_options.dimension) - throw std::invalid_argument( - "The mapping workspace has the wrong dimension." - ); + throw std::invalid_argument("The mapping workspace has the wrong dimension."); if (transformation.GetSpaceDim() != m_options.dimension) - throw std::invalid_argument( - "The element transformation has the wrong spatial dimension." - ); - if (transformation.GetGeometryType() != - element_data.displacement.GetElement().GetGeomType()) + throw std::invalid_argument("The element transformation has the wrong spatial dimension."); + if (transformation.GetGeometryType() != element_data.displacement.GetElement().GetGeomType()) throw std::invalid_argument( "The element transformation geometry does not match the " "supplied " @@ -432,12 +384,11 @@ namespace mean_field::mapping { transformation.Transform(integration_point, context.reference_position); EvaluateField( - element_data.displacement, transformation, integration_point, - workspace, workspace.m_field_value, workspace.m_field_jacobian + element_data.displacement, transformation, integration_point, workspace, workspace.m_field_value, + workspace.m_field_jacobian ); - if (!vector_is_finite(context.reference_position) || - !vector_is_finite(workspace.m_field_value) || + if (!vector_is_finite(context.reference_position) || !vector_is_finite(workspace.m_field_value) || !matrix_is_finite(workspace.m_field_jacobian)) { return MappingStatus::non_finite_input; } @@ -446,9 +397,7 @@ namespace mean_field::mapping { context.displaced_position = context.reference_position; context.displaced_position += workspace.m_field_value; - context.displacement_jacobian.SetSize( - m_options.dimension, m_options.dimension - ); + context.displacement_jacobian.SetSize(m_options.dimension, m_options.dimension); context.displacement_jacobian = workspace.m_field_jacobian; for (int i = 0; i < m_options.dimension; ++i) context.displacement_jacobian(i, i) += 1.0; @@ -456,43 +405,34 @@ namespace mean_field::mapping { context.compactified = IsCompactifiedElement(transformation); if (context.compactified) { - const MappingStatus coordinate_status = - EvaluateCompactificationCoordinate( - element_data.compactification, transformation, - integration_point, workspace, - workspace.m_compactification_point - ); + const MappingStatus coordinate_status = EvaluateCompactificationCoordinate( + element_data.compactification, transformation, integration_point, workspace, + workspace.m_compactification_point + ); if (coordinate_status != MappingStatus::valid) return coordinate_status; const compactification::ExteriorMapInput exterior_input{ - .reference_position = context.reference_position, - .displaced_position = context.displaced_position, - .displacement_jacobian = context.displacement_jacobian, - .compactification_coordinate = - workspace.m_compactification_point.coordinate, - .compactification_coordinate_gradient = - workspace.m_compactification_point.coordinate_gradient + .reference_position = context.reference_position, + .displaced_position = context.displaced_position, + .displacement_jacobian = context.displacement_jacobian, + .compactification_coordinate = workspace.m_compactification_point.coordinate, + .compactification_coordinate_gradient = workspace.m_compactification_point.coordinate_gradient }; - const MappingStatus exterior_status = m_exterior_map->Evaluate( - exterior_input, workspace.m_exterior_result - ); + const MappingStatus exterior_status = m_exterior_map->Evaluate(exterior_input, workspace.m_exterior_result); if (exterior_status != MappingStatus::valid) return exterior_status; - context.physical_position = - workspace.m_exterior_result.physical_position; - context.mapping_jacobian = - workspace.m_exterior_result.mapping_jacobian; + context.physical_position = workspace.m_exterior_result.physical_position; + context.mapping_jacobian = workspace.m_exterior_result.mapping_jacobian; } else { context.physical_position = context.displaced_position; context.mapping_jacobian = context.displacement_jacobian; } - if (!vector_is_finite(context.physical_position) || - !matrix_is_finite(context.mapping_jacobian)) + if (!vector_is_finite(context.physical_position) || !matrix_is_finite(context.mapping_jacobian)) return MappingStatus::non_finite_result; context.mapping_determinant = context.mapping_jacobian.Det(); @@ -501,12 +441,8 @@ namespace mean_field::mapping { if (context.mapping_determinant <= 0.0) return MappingStatus::non_positive_determinant; - context.inverse_mapping_jacobian.SetSize( - m_options.dimension, m_options.dimension - ); - mfem::CalcInverse( - context.mapping_jacobian, context.inverse_mapping_jacobian - ); + context.inverse_mapping_jacobian.SetSize(m_options.dimension, m_options.dimension); + mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian); if (!matrix_is_finite(context.inverse_mapping_jacobian)) return MappingStatus::non_finite_result; @@ -521,33 +457,22 @@ namespace mean_field::mapping { Workspace &workspace, VolumeMappingContext &context ) const { - const MappingStatus point_status = EvaluatePoint( - element_data, transformation, integration_point, workspace, - context.mapping - ); + const MappingStatus point_status = + EvaluatePoint(element_data, transformation, integration_point, workspace, context.mapping); if (point_status != MappingStatus::valid) return point_status; transformation.SetIntPoint(&integration_point); - mfem::Mult( - context.mapping.mapping_jacobian, transformation.Jacobian(), - workspace.m_full_element_jacobian - ); + mfem::Mult(context.mapping.mapping_jacobian, transformation.Jacobian(), workspace.m_full_element_jacobian); - context.quadrature.J_inv.SetSize( - m_options.dimension, m_options.dimension - ); - mfem::CalcInverse( - workspace.m_full_element_jacobian, context.quadrature.J_inv - ); + context.quadrature.J_inv.SetSize(m_options.dimension, m_options.dimension); + mfem::CalcInverse(workspace.m_full_element_jacobian, context.quadrature.J_inv); - context.quadrature.detJ = context.mapping.mapping_determinant; - context.quadrature.weight = integration_point.weight * - transformation.Weight() * - context.mapping.mapping_determinant; + context.quadrature.detJ = context.mapping.mapping_determinant; + context.quadrature.weight = + integration_point.weight * transformation.Weight() * context.mapping.mapping_determinant; - if (!matrix_is_finite(context.quadrature.J_inv) || - !std::isfinite(context.quadrature.weight)) + if (!matrix_is_finite(context.quadrature.J_inv) || !std::isfinite(context.quadrature.weight)) return MappingStatus::non_finite_result; if (context.quadrature.weight <= 0.0) return MappingStatus::non_positive_determinant; @@ -555,33 +480,24 @@ namespace mean_field::mapping { return MappingStatus::valid; } - mfem::ElementTransformation & - DomainMapperStateless::SelectFaceElementTransformation( + mfem::ElementTransformation &DomainMapperStateless::SelectFaceElementTransformation( mfem::FaceElementTransformations &transformation, const FaceElementSide side ) { if (side == FaceElementSide::element_1) { - MFEM_VERIFY( - transformation.Elem1 != nullptr, - "The face does not have an element-1 transformation." - ); + MFEM_VERIFY(transformation.Elem1 != nullptr, "The face does not have an element-1 transformation."); return *transformation.Elem1; } - MFEM_VERIFY( - transformation.Elem2 != nullptr, - "The face does not have an element-2 transformation." - ); + MFEM_VERIFY(transformation.Elem2 != nullptr, "The face does not have an element-2 transformation."); return *transformation.Elem2; } - const mfem::IntegrationPoint & - DomainMapperStateless::SelectFaceElementIntegrationPoint( + const mfem::IntegrationPoint &DomainMapperStateless::SelectFaceElementIntegrationPoint( mfem::FaceElementTransformations &transformation, const FaceElementSide side ) { - mfem::ElementTransformation &element_transformation = - SelectFaceElementTransformation(transformation, side); + mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side); return element_transformation.GetIntPoint(); } @@ -594,63 +510,47 @@ namespace mean_field::mapping { FaceMappingContext &context ) const { transformation.SetAllIntPoints(&integration_point); - mfem::ElementTransformation &element_transformation = - SelectFaceElementTransformation(transformation, side); + mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side); const mfem::IntegrationPoint &element_integration_point = SelectFaceElementIntegrationPoint(transformation, side); - const MappingStatus point_status = EvaluatePoint( - element_data, element_transformation, element_integration_point, - workspace, context.mapping - ); + const MappingStatus point_status = + EvaluatePoint(element_data, element_transformation, element_integration_point, workspace, context.mapping); if (point_status != MappingStatus::valid) return point_status; workspace.m_reference_normal.SetSize(m_options.dimension); - mfem::CalcOrtho( - transformation.Jacobian(), workspace.m_reference_normal - ); + mfem::CalcOrtho(transformation.Jacobian(), workspace.m_reference_normal); if (side == FaceElementSide::element_2) workspace.m_reference_normal *= -1.0; - const double reference_normal_magnitude = - workspace.m_reference_normal.Norml2(); - if (!std::isfinite(reference_normal_magnitude) || - reference_normal_magnitude <= 0.0) + const double reference_normal_magnitude = workspace.m_reference_normal.Norml2(); + if (!std::isfinite(reference_normal_magnitude) || reference_normal_magnitude <= 0.0) return MappingStatus::non_finite_result; context.reference_normal.SetSize(m_options.dimension); context.reference_normal = workspace.m_reference_normal; context.reference_normal /= reference_normal_magnitude; - context.mapping.inverse_mapping_jacobian.MultTranspose( - workspace.m_reference_normal, workspace.m_mapped_normal - ); + context.mapping.inverse_mapping_jacobian.MultTranspose(workspace.m_reference_normal, workspace.m_mapped_normal); workspace.m_mapped_normal *= context.mapping.mapping_determinant; - const double mapped_normal_magnitude = - workspace.m_mapped_normal.Norml2(); - if (!std::isfinite(mapped_normal_magnitude) || - mapped_normal_magnitude <= 0.0) + const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2(); + if (!std::isfinite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0.0) return MappingStatus::non_finite_result; context.quadrature.normal.SetSize(m_options.dimension); context.quadrature.normal = workspace.m_mapped_normal; context.quadrature.normal /= mapped_normal_magnitude; - context.reference_surface_weight = - integration_point.weight * reference_normal_magnitude; - context.physical_surface_weight = - integration_point.weight * mapped_normal_magnitude; + context.reference_surface_weight = integration_point.weight * reference_normal_magnitude; + context.physical_surface_weight = integration_point.weight * mapped_normal_magnitude; - context.quadrature.ds = context.reference_surface_weight; - context.quadrature.v_dot_n_scale = - mapped_normal_magnitude / reference_normal_magnitude; + context.quadrature.ds = context.reference_surface_weight; + context.quadrature.v_dot_n_scale = mapped_normal_magnitude / reference_normal_magnitude; - if (!vector_is_finite(context.quadrature.normal) || - !std::isfinite(context.reference_surface_weight) || - !std::isfinite(context.physical_surface_weight) || - !std::isfinite(context.quadrature.v_dot_n_scale)) { + if (!vector_is_finite(context.quadrature.normal) || !std::isfinite(context.reference_surface_weight) || + !std::isfinite(context.physical_surface_weight) || !std::isfinite(context.quadrature.v_dot_n_scale)) { return MappingStatus::non_finite_result; } @@ -668,8 +568,7 @@ namespace mean_field::mapping { ) const { ValidateElementData(element_data); const ElementMappingData direction_data{ - .displacement = direction, - .compactification = element_data.compactification + .displacement = direction, .compactification = element_data.compactification }; ValidateElementData(direction_data); @@ -679,9 +578,7 @@ namespace mean_field::mapping { "degree-of-freedom counts." ); if (workspace.GetDimension() != m_options.dimension) - throw std::invalid_argument( - "The mapping workspace has the wrong dimension." - ); + throw std::invalid_argument("The mapping workspace has the wrong dimension."); if (base_context.compactified != IsCompactifiedElement(transformation)) throw std::invalid_argument( "The base mapping context does not match the current element " @@ -689,86 +586,66 @@ namespace mean_field::mapping { ); EvaluateField( - direction, transformation, integration_point, workspace, - workspace.m_field_value, workspace.m_field_jacobian + direction, transformation, integration_point, workspace, workspace.m_field_value, workspace.m_field_jacobian ); - if (!vector_is_finite(workspace.m_field_value) || - !matrix_is_finite(workspace.m_field_jacobian)) + if (!vector_is_finite(workspace.m_field_value) || !matrix_is_finite(workspace.m_field_jacobian)) return MappingStatus::non_finite_input; variation.displacement_variation = workspace.m_field_value; variation.displacement_jacobian_variation = workspace.m_field_jacobian; if (base_context.compactified) { - const MappingStatus coordinate_status = - EvaluateCompactificationCoordinate( - element_data.compactification, transformation, - integration_point, workspace, - workspace.m_compactification_point - ); + const MappingStatus coordinate_status = EvaluateCompactificationCoordinate( + element_data.compactification, transformation, integration_point, workspace, + workspace.m_compactification_point + ); if (coordinate_status != MappingStatus::valid) return coordinate_status; const compactification::ExteriorMapInput exterior_input{ - .reference_position = base_context.reference_position, - .displaced_position = base_context.displaced_position, - .displacement_jacobian = base_context.displacement_jacobian, - .compactification_coordinate = - workspace.m_compactification_point.coordinate, - .compactification_coordinate_gradient = - workspace.m_compactification_point.coordinate_gradient + .reference_position = base_context.reference_position, + .displaced_position = base_context.displaced_position, + .displacement_jacobian = base_context.displacement_jacobian, + .compactification_coordinate = workspace.m_compactification_point.coordinate, + .compactification_coordinate_gradient = workspace.m_compactification_point.coordinate_gradient }; - workspace.m_exterior_result.physical_position = - base_context.physical_position; - workspace.m_exterior_result.mapping_jacobian = - base_context.mapping_jacobian; + workspace.m_exterior_result.physical_position = base_context.physical_position; + workspace.m_exterior_result.mapping_jacobian = base_context.mapping_jacobian; const compactification::ExteriorMapDirection exterior_direction{ - .displaced_position_variation = - variation.displacement_variation, - .displacement_jacobian_variation = - variation.displacement_jacobian_variation + .displaced_position_variation = variation.displacement_variation, + .displacement_jacobian_variation = variation.displacement_jacobian_variation }; // ReSharper disable once CppTooWideScopeInitStatement - const MappingStatus exterior_status = - m_exterior_map->EvaluateVariation( - exterior_input, workspace.m_exterior_result, - exterior_direction, workspace.m_exterior_variation - ); + const MappingStatus exterior_status = m_exterior_map->EvaluateVariation( + exterior_input, workspace.m_exterior_result, exterior_direction, workspace.m_exterior_variation + ); if (exterior_status != MappingStatus::valid) { return exterior_status; } - variation.physical_position_variation = - workspace.m_exterior_variation.physical_position_variation; - variation.mapping_jacobian_variation = - workspace.m_exterior_variation.mapping_jacobian_variation; + variation.physical_position_variation = workspace.m_exterior_variation.physical_position_variation; + variation.mapping_jacobian_variation = workspace.m_exterior_variation.mapping_jacobian_variation; } else { - variation.physical_position_variation = - variation.displacement_variation; - variation.mapping_jacobian_variation = - variation.displacement_jacobian_variation; + variation.physical_position_variation = variation.displacement_variation; + variation.mapping_jacobian_variation = variation.displacement_jacobian_variation; } mfem::Mult( - base_context.inverse_mapping_jacobian, - variation.mapping_jacobian_variation, workspace.m_matrix_temp_1 + base_context.inverse_mapping_jacobian, variation.mapping_jacobian_variation, workspace.m_matrix_temp_1 ); double trace = 0.0; for (int i = 0; i < m_options.dimension; ++i) trace += workspace.m_matrix_temp_1(i, i); - variation.mapping_determinant_variation = - base_context.mapping_determinant * trace; + variation.mapping_determinant_variation = base_context.mapping_determinant * trace; - variation.inverse_mapping_jacobian_variation.SetSize( - m_options.dimension, m_options.dimension - ); + variation.inverse_mapping_jacobian_variation.SetSize(m_options.dimension, m_options.dimension); mfem::Mult( workspace.m_matrix_temp_1, base_context.inverse_mapping_jacobian, variation.inverse_mapping_jacobian_variation @@ -795,34 +672,26 @@ namespace mean_field::mapping { VolumeMappingVariation &variation ) const { const MappingStatus point_status = EvaluatePointVariation( - element_data, direction, transformation, integration_point, - base_context.mapping, workspace, variation.mapping + element_data, direction, transformation, integration_point, base_context.mapping, workspace, + variation.mapping ); if (point_status != MappingStatus::valid) return point_status; transformation.SetIntPoint(&integration_point); mfem::Mult( - variation.mapping.mapping_jacobian_variation, - transformation.Jacobian(), workspace.m_full_element_jacobian - ); - mfem::Mult( - base_context.quadrature.J_inv, workspace.m_full_element_jacobian, - workspace.m_matrix_temp_1 + variation.mapping.mapping_jacobian_variation, transformation.Jacobian(), workspace.m_full_element_jacobian ); + mfem::Mult(base_context.quadrature.J_inv, workspace.m_full_element_jacobian, workspace.m_matrix_temp_1); - variation.inverse_element_jacobian_variation.SetSize( - m_options.dimension, m_options.dimension - ); + variation.inverse_element_jacobian_variation.SetSize(m_options.dimension, m_options.dimension); mfem::Mult( - workspace.m_matrix_temp_1, base_context.quadrature.J_inv, - variation.inverse_element_jacobian_variation + workspace.m_matrix_temp_1, base_context.quadrature.J_inv, variation.inverse_element_jacobian_variation ); variation.inverse_element_jacobian_variation *= -1.0; variation.weight_variation = - integration_point.weight * transformation.Weight() * - variation.mapping.mapping_determinant_variation; + integration_point.weight * transformation.Weight() * variation.mapping.mapping_determinant_variation; if (!matrix_is_finite(variation.inverse_element_jacobian_variation) || !std::isfinite(variation.weight_variation)) @@ -842,30 +711,24 @@ namespace mean_field::mapping { FaceMappingVariation &variation ) const { transformation.SetAllIntPoints(&integration_point); - mfem::ElementTransformation &element_transformation = - SelectFaceElementTransformation(transformation, side); + mfem::ElementTransformation &element_transformation = SelectFaceElementTransformation(transformation, side); const mfem::IntegrationPoint &element_integration_point = SelectFaceElementIntegrationPoint(transformation, side); const MappingStatus point_status = EvaluatePointVariation( - element_data, direction, element_transformation, - element_integration_point, base_context.mapping, workspace, + element_data, direction, element_transformation, element_integration_point, base_context.mapping, workspace, variation.mapping ); if (point_status != MappingStatus::valid) return point_status; workspace.m_reference_normal.SetSize(m_options.dimension); - mfem::CalcOrtho( - transformation.Jacobian(), workspace.m_reference_normal - ); + mfem::CalcOrtho(transformation.Jacobian(), workspace.m_reference_normal); if (side == FaceElementSide::element_2) workspace.m_reference_normal *= -1.0; - const double reference_normal_magnitude = - workspace.m_reference_normal.Norml2(); - if (!std::isfinite(reference_normal_magnitude) || - reference_normal_magnitude <= 0.0) + const double reference_normal_magnitude = workspace.m_reference_normal.Norml2(); + if (!std::isfinite(reference_normal_magnitude) || reference_normal_magnitude <= 0.0) return MappingStatus::non_finite_result; base_context.mapping.inverse_mapping_jacobian.MultTranspose( @@ -878,32 +741,23 @@ namespace mean_field::mapping { variation.mapping.inverse_mapping_jacobian_variation.MultTranspose( workspace.m_reference_normal, variation.physical_normal_variation ); - variation.physical_normal_variation *= - base_context.mapping.mapping_determinant; + variation.physical_normal_variation *= base_context.mapping.mapping_determinant; variation.physical_normal_variation.Add( - variation.mapping.mapping_determinant_variation, - workspace.m_vector_temp + variation.mapping.mapping_determinant_variation, workspace.m_vector_temp ); - const double mapped_normal_magnitude = - workspace.m_mapped_normal.Norml2(); - if (!std::isfinite(mapped_normal_magnitude) || - mapped_normal_magnitude <= 0.0) + const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2(); + if (!std::isfinite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0.0) return MappingStatus::non_finite_result; const double mapped_normal_magnitude_variation = - base_context.quadrature.normal * - variation.physical_normal_variation; + base_context.quadrature.normal * variation.physical_normal_variation; - variation.physical_normal_variation.Add( - -mapped_normal_magnitude_variation, base_context.quadrature.normal - ); + variation.physical_normal_variation.Add(-mapped_normal_magnitude_variation, base_context.quadrature.normal); variation.physical_normal_variation /= mapped_normal_magnitude; - variation.physical_surface_weight_variation = - integration_point.weight * mapped_normal_magnitude_variation; - variation.normal_flux_scale_variation = - mapped_normal_magnitude_variation / reference_normal_magnitude; + variation.physical_surface_weight_variation = integration_point.weight * mapped_normal_magnitude_variation; + variation.normal_flux_scale_variation = mapped_normal_magnitude_variation / reference_normal_magnitude; if (!vector_is_finite(variation.physical_normal_variation) || !std::isfinite(variation.physical_surface_weight_variation) || diff --git a/libmeanfield/impl/mapping/transformations.cpp b/libmeanfield/impl/mapping/transformations.cpp index e41f963..c0936a7 100644 --- a/libmeanfield/impl/mapping/transformations.cpp +++ b/libmeanfield/impl/mapping/transformations.cpp @@ -41,15 +41,12 @@ namespace mean_field::mapping { mfem::Vector &physical_gradient ) { MFEM_VERIFY( - reference_gradient.Size() == - context.inverse_mapping_jacobian.Height(), + reference_gradient.Size() == context.inverse_mapping_jacobian.Height(), "The reference scalar gradient has the wrong dimension." ); physical_gradient.SetSize(reference_gradient.Size()); - context.inverse_mapping_jacobian.MultTranspose( - reference_gradient, physical_gradient - ); + context.inverse_mapping_jacobian.MultTranspose(reference_gradient, physical_gradient); } void MapPhysicalGradientToReference( @@ -63,9 +60,7 @@ namespace mean_field::mapping { ); reference_gradient.SetSize(physical_gradient.Size()); - context.mapping_jacobian.MultTranspose( - physical_gradient, reference_gradient - ); + context.mapping_jacobian.MultTranspose(physical_gradient, reference_gradient); } void MapReferenceVectorGradientToPhysical( @@ -74,19 +69,12 @@ namespace mean_field::mapping { mfem::DenseMatrix &physical_gradient ) { MFEM_VERIFY( - reference_gradient.Width() == - context.inverse_mapping_jacobian.Height(), + reference_gradient.Width() == context.inverse_mapping_jacobian.Height(), "The reference vector gradient has the wrong dimension." ); - physical_gradient.SetSize( - reference_gradient.Height(), - context.inverse_mapping_jacobian.Width() - ); - mfem::Mult( - reference_gradient, context.inverse_mapping_jacobian, - physical_gradient - ); + physical_gradient.SetSize(reference_gradient.Height(), context.inverse_mapping_jacobian.Width()); + mfem::Mult(reference_gradient, context.inverse_mapping_jacobian, physical_gradient); } void MapPhysicalVectorGradientToReference( @@ -99,12 +87,8 @@ namespace mean_field::mapping { "The physical vector gradient has the wrong dimension." ); - reference_gradient.SetSize( - physical_gradient.Height(), context.mapping_jacobian.Width() - ); - mfem::Mult( - physical_gradient, context.mapping_jacobian, reference_gradient - ); + reference_gradient.SetSize(physical_gradient.Height(), context.mapping_jacobian.Width()); + mfem::Mult(physical_gradient, context.mapping_jacobian, reference_gradient); } double MapHDivDivergenceToPhysical( @@ -120,19 +104,11 @@ namespace mean_field::mapping { ) { const int dimension = context.mapping_jacobian.Height(); - MFEM_VERIFY( - context.mapping_jacobian.Width() == dimension, - "The mapping Jacobian must be square." - ); - MFEM_VERIFY( - context.mapping_determinant > 0.0, - "The mapping determinant must be positive." - ); + MFEM_VERIFY(context.mapping_jacobian.Width() == dimension, "The mapping Jacobian must be square."); + MFEM_VERIFY(context.mapping_determinant > 0.0, "The mapping determinant must be positive."); mass_tensor.SetSize(dimension, dimension); - mfem::MultAtB( - context.mapping_jacobian, context.mapping_jacobian, mass_tensor - ); + mfem::MultAtB(context.mapping_jacobian, context.mapping_jacobian, mass_tensor); mass_tensor *= 1 / context.mapping_determinant; } @@ -143,19 +119,12 @@ namespace mean_field::mapping { const int dimension = context.inverse_mapping_jacobian.Height(); MFEM_VERIFY( - context.inverse_mapping_jacobian.Width() == dimension, - "The inverse mapping Jacobian must be square." - ); - MFEM_VERIFY( - context.mapping_determinant > 0.0, - "The mapping determinant must be positive." + context.inverse_mapping_jacobian.Width() == dimension, "The inverse mapping Jacobian must be square." ); + MFEM_VERIFY(context.mapping_determinant > 0.0, "The mapping determinant must be positive."); diffusion_tensor.SetSize(dimension, dimension); - mfem::MultABt( - context.inverse_mapping_jacobian, context.inverse_mapping_jacobian, - diffusion_tensor - ); + mfem::MultABt(context.inverse_mapping_jacobian, context.inverse_mapping_jacobian, diffusion_tensor); diffusion_tensor *= context.mapping_determinant; } @@ -170,9 +139,7 @@ namespace mean_field::mapping { ); physical_field.SetSize(reference_field.Size()); - context.inverse_mapping_jacobian.MultTranspose( - reference_field, physical_field - ); + context.inverse_mapping_jacobian.MultTranspose(reference_field, physical_field); } void MapPhysicalFieldToHCurlReference( @@ -239,35 +206,24 @@ namespace mean_field::mapping { const MappingPointVariation &variation, mfem::DenseMatrix &mass_tensor_variation ) { - const double determinant = context.mapping_determinant; - const double determinant_variation = - variation.mapping_determinant_variation; - const int dimension = context.inverse_mapping_jacobian.Width(); + const double determinant = context.mapping_determinant; + const double determinant_variation = variation.mapping_determinant_variation; + const int dimension = context.inverse_mapping_jacobian.Width(); mass_tensor_variation.SetSize(dimension, dimension); MFEM_VERIFY( - std::isfinite(determinant) && determinant > 0.0, - "The mapping determinant must be positive and finite." - ); - MFEM_VERIFY( - std::isfinite(determinant_variation), - "The mapping determinant variation must be finite." + std::isfinite(determinant) && determinant > 0.0, "The mapping determinant must be positive and finite." ); + MFEM_VERIFY(std::isfinite(determinant_variation), "The mapping determinant variation must be finite."); mfem::DenseMatrix determinant_correction(dimension, dimension); ComputeHDivMassTensor(context, determinant_correction); determinant_correction *= determinant_variation / determinant; mfem::DenseMatrix right_jacobian_variation(dimension, dimension); - mfem::MultAtB( - context.mapping_jacobian, variation.mapping_jacobian_variation, - right_jacobian_variation - ); - mfem::MultAtB( - variation.mapping_jacobian_variation, context.mapping_jacobian, - mass_tensor_variation - ); + mfem::MultAtB(context.mapping_jacobian, variation.mapping_jacobian_variation, right_jacobian_variation); + mfem::MultAtB(variation.mapping_jacobian_variation, context.mapping_jacobian, mass_tensor_variation); mass_tensor_variation += right_jacobian_variation; mass_tensor_variation *= 1 / determinant; diff --git a/libmeanfield/impl/models/polytropic.cpp b/libmeanfield/impl/models/polytropic.cpp new file mode 100644 index 0000000..e2eee07 --- /dev/null +++ b/libmeanfield/impl/models/polytropic.cpp @@ -0,0 +1,260 @@ +module; +#include +#include +#include +module mean_field; +import :model.structure.polytropic; + +namespace mean_field::models::structure { + PolytropicStructure::PolytropicStructure( + eos::Polytrope equationOfState, + const double targetMass + ) + : m_equationOfState(std::move(equationOfState)), + m_targetMass(targetMass) { + validate(); + } + + const eos::EquationOfState &PolytropicStructure::equationOfState() const noexcept { + return m_equationOfState; + } + + double PolytropicStructure::targetMass() const noexcept { + return m_targetMass; + } + + StructureSeed PolytropicStructure::makeInitialSeed(const StructureSeedRequest &request) const { + validateSeedRequest(request); + + const double polytropicIndex = m_equationOfState.polytropic_index(); + const std::vector laneEmdenSolution = solveLaneEmden(polytropicIndex); + const double surfaceCoordinate = laneEmdenSolution.back().coordinate; + const double centralEnthalpy = m_equationOfState.enthalpy_from_density(request.centralDensity); + const double radialScaleSquared = + centralEnthalpy / (4.0 * std::numbers::pi_v * mean_field::utils::G * request.centralDensity); + + if (!std::isfinite(radialScaleSquared) || radialScaleSquared <= 0.0) { + throw std::runtime_error( + "The polytropic Lane-Emden radial scale is not " + "finite and positive." + ); + } + + const double radialScale = std::sqrt(radialScaleSquared); + + StructureSeed seed; + + seed.radius.SetSize(request.radialSampleCount); + seed.density.SetSize(request.radialSampleCount); + seed.enthalpy.SetSize(request.radialSampleCount); + + seed.stellarRadius = radialScale * surfaceCoordinate; + seed.centralDensity = request.centralDensity; + seed.centralEnthalpy = centralEnthalpy; + + std::size_t interpolationIndex = 0; + + for (int sampleIndex = 0; sampleIndex < request.radialSampleCount; ++sampleIndex) { + const double sampleFraction = + static_cast(sampleIndex) / static_cast(request.radialSampleCount - 1); + + const double dimensionlessRadius = sampleFraction * surfaceCoordinate; + const double laneEmdenValue = + interpolateLaneEmdenValue(laneEmdenSolution, dimensionlessRadius, interpolationIndex); + const double density = request.centralDensity * std::pow(laneEmdenValue, polytropicIndex); + + seed.radius(sampleIndex) = radialScale * dimensionlessRadius; + seed.density(sampleIndex) = density; + seed.enthalpy(sampleIndex) = m_equationOfState.enthalpy_from_density(density); + } + + seed.radius(0) = 0.0; + seed.density(0) = request.centralDensity; + seed.enthalpy(0) = centralEnthalpy; + + const int surfaceIndex = request.radialSampleCount - 1; + + seed.radius(surfaceIndex) = seed.stellarRadius; + seed.density(surfaceIndex) = 0.0; + seed.enthalpy(surfaceIndex) = 0.0; + + return seed; + } + + void PolytropicStructure::validate() const { + const double polytropicIndex = m_equationOfState.polytropic_index(); + + if (!std::isfinite(polytropicIndex) || polytropicIndex < 1.0 || polytropicIndex >= 5.0) { + throw std::invalid_argument( + std::format( + "PolytropicStructure requires a finite-radius " + "polytrope with 1 <= n < 5. Instead n = {} was " + "provided.", + polytropicIndex + ) + ); + } + + if (!std::isfinite(m_targetMass) || m_targetMass <= 0.0) { + throw std::invalid_argument( + std::format( + "The target stellar mass must be finite and " + "positive. Instead a value of {} was provided.", + m_targetMass + ) + ); + } + } + + void PolytropicStructure::validateSeedRequest(const StructureSeedRequest &request) { + if (!std::isfinite(request.centralDensity) || request.centralDensity <= 0.0) { + throw std::invalid_argument( + std::format( + "The seed central density must be finite and " + "positive. Instead a value of {} was provided.", + request.centralDensity + ) + ); + } + + if (request.radialSampleCount < 2) { + throw std::invalid_argument( + std::format( + "A polytropic seed requires at least two radial " + "samples. Instead {} samples were requested.", + request.radialSampleCount + ) + ); + } + } + + PolytropicStructure::LaneEmdenDerivative PolytropicStructure::evaluateLaneEmdenRhs( + const double coordinate, + const double value, + const double derivative, + const double polytropicIndex + ) { + const double nonnegativeValue = std::max(value, 0.0); + + return { + .value = derivative, + .derivative = -2.0 * derivative / coordinate - std::pow(nonnegativeValue, polytropicIndex) + }; + } + + PolytropicStructure::LaneEmdenPoint PolytropicStructure::takeLaneEmdenStep( + const LaneEmdenPoint &point, + const double step, + const double polytropicIndex + ) { + const LaneEmdenDerivative first = + evaluateLaneEmdenRhs(point.coordinate, point.value, point.derivative, polytropicIndex); + + const LaneEmdenDerivative second = evaluateLaneEmdenRhs( + point.coordinate + 0.5 * step, point.value + 0.5 * step * first.value, + point.derivative + 0.5 * step * first.derivative, polytropicIndex + ); + + const LaneEmdenDerivative third = evaluateLaneEmdenRhs( + point.coordinate + 0.5 * step, point.value + 0.5 * step * second.value, + point.derivative + 0.5 * step * second.derivative, polytropicIndex + ); + + const LaneEmdenDerivative fourth = evaluateLaneEmdenRhs( + point.coordinate + step, point.value + step * third.value, point.derivative + step * third.derivative, + polytropicIndex + ); + + return { + .coordinate = point.coordinate + step, + .value = point.value + step / 6.0 * (first.value + 2.0 * second.value + 2.0 * third.value + fourth.value), + .derivative = + point.derivative + + step / 6.0 * (first.derivative + 2.0 * second.derivative + 2.0 * third.derivative + fourth.derivative) + }; + } + + std::vector PolytropicStructure::solveLaneEmden(const double polytropicIndex) { + constexpr double initialCoordinate = 1.0e-6; + constexpr double integrationStep = 1.0e-3; + constexpr int maximumStepCount = 2'000'000; + + const double coordinateSquared = initialCoordinate * initialCoordinate; + + const double coordinateCubed = coordinateSquared * initialCoordinate; + + const double coordinateFourth = coordinateSquared * coordinateSquared; + + LaneEmdenPoint point{ + .coordinate = initialCoordinate, + .value = 1.0 - coordinateSquared / 6.0 + polytropicIndex * coordinateFourth / 120.0, + .derivative = -initialCoordinate / 3.0 + polytropicIndex * coordinateCubed / 30.0 + }; + + std::vector solution; + solution.reserve(8192); + solution.push_back({.coordinate = 0.0, .value = 1.0, .derivative = 0.0}); + solution.push_back(point); + + for (int stepIndex = 0; stepIndex < maximumStepCount; ++stepIndex) { + LaneEmdenPoint nextPoint = takeLaneEmdenStep(point, integrationStep, polytropicIndex); + + if (!std::isfinite(nextPoint.value)) { + throw std::runtime_error( + "The Lane-Emden integration produced a non-finite " + "solution before reaching the stellar surface." + ); + } + + if (nextPoint.value <= 0.0) { + const double rootFraction = point.value / (point.value - nextPoint.value); + + solution.push_back( + {.coordinate = point.coordinate + rootFraction * (nextPoint.coordinate - point.coordinate), + .value = 0.0, + .derivative = point.derivative + rootFraction * (nextPoint.derivative - point.derivative)} + ); + + return solution; + } + + solution.push_back(nextPoint); + point = nextPoint; + } + + throw std::runtime_error( + "The Lane-Emden integration did not reach its first zero " + "within the configured step limit." + ); + } + + double PolytropicStructure::interpolateLaneEmdenValue( + const std::vector &solution, + const double coordinate, + std::size_t &lowerIndex + ) { + while (lowerIndex + 1 < solution.size() && solution[lowerIndex + 1].coordinate < coordinate) { + ++lowerIndex; + } + + if (lowerIndex + 1 >= solution.size()) { + return 0.0; + } + + const LaneEmdenPoint &lower = solution[lowerIndex]; + const LaneEmdenPoint &upper = solution[lowerIndex + 1]; + + const double interval = upper.coordinate - lower.coordinate; + + if (interval <= 0.0) { + throw std::runtime_error( + "The Lane-Emden interpolation grid is not strictly " + "increasing." + ); + } + + const double fraction = (coordinate - lower.coordinate) / interval; + + return std::clamp(lower.value + fraction * (upper.value - lower.value), 0.0, 1.0); + } +}; // namespace mean_field::models::structure diff --git a/libmeanfield/impl/operators/contexts/barotropic_closure_linearization_context.cpp b/libmeanfield/impl/operators/contexts/barotropic_closure_linearization_context.cpp index a72cbd3..29d8802 100644 --- a/libmeanfield/impl/operators/contexts/barotropic_closure_linearization_context.cpp +++ b/libmeanfield/impl/operators/contexts/barotropic_closure_linearization_context.cpp @@ -1,135 +1,190 @@ module; -#include +#include #include module mean_field; + import :operators.context.barotropic_closure_linearization; +namespace { + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int i = 0; i < vector.Size(); ++i) { + MFEM_VERIFY(std::isfinite(vector(i)), message); + } + } + + template + void validate_dependency_transition( + const Stamp &prepared, + const Stamp &requested, + const char *message + ) { + MFEM_VERIFY(requested.CanFollow(prepared), message); + MFEM_VERIFY( + prepared.identity == requested.identity || prepared.revision != requested.revision, + "A new barotropic-closure dependency identity must also carry a visibly different revision." + ); + } +} // namespace + namespace mean_field::operators::context::barotropic { - BarotropicClosureLinearizationContext:: - BarotropicClosureLinearizationContext( - const fem::FEM &f, - const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope - ) + BarotropicClosureLinearizationContext::BarotropicClosureLinearizationContext( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const field::FieldDofMap &densityMap, + const field::FieldDofMap &enthalpyMap, + const field::FieldDofMap &displacementMap + ) : m_f(f), - m_operator( - f, - domainMapper, - barotrope - ) { + m_domainMapper(domainMapper), + m_densitySize(densityMap.reduced_size()), + m_enthalpySize(enthalpyMap.reduced_size()), + m_displacementSize(displacementMap.reduced_size()) { + MFEM_VERIFY(m_f.mesh != nullptr, "BarotropicClosureLinearizationContext requires a mesh."); + MFEM_VERIFY(m_f.densityFes != nullptr, "BarotropicClosureLinearizationContext requires the density FE space."); MFEM_VERIFY( - m_f.densityFes != nullptr, - "The closure linearization context requires the " - "density finite-element space." + m_f.enthalpyFes != nullptr, "BarotropicClosureLinearizationContext requires the enthalpy FE space." ); - MFEM_VERIFY( - m_f.enthalpyFes != nullptr, - "The closure linearization context requires the " - "enthalpy finite-element space." + m_f.displacementFes != nullptr, "BarotropicClosureLinearizationContext requires the displacement FE space." ); - MFEM_VERIFY( - m_f.displacementFes != nullptr, - "The closure linearization context requires the " - "displacement finite-element space." + m_domainMapper.GetDimension() == m_f.mesh->Dimension(), + "The barotropic-closure context domain-mapper dimension does not match the mesh dimension." + ); + MFEM_VERIFY( + densityMap.full_size() == m_f.densityFes->GetTrueVSize(), + "The density FieldDofMap does not match the density FE space." + ); + MFEM_VERIFY( + enthalpyMap.full_size() == m_f.enthalpyFes->GetTrueVSize(), + "The enthalpy FieldDofMap does not match the enthalpy FE space." + ); + MFEM_VERIFY( + displacementMap.full_size() == m_f.displacementFes->GetTrueVSize(), + "The displacement FieldDofMap does not match the displacement FE space." ); } - void BarotropicClosureLinearizationContext::Prepare( - const mfem::Vector &baseDensityTrue, - const mfem::Vector &baseEnthalpyTrue, - const mfem::Vector &displacementTrue, - const BarotropicClosureRevisions &revisions + BarotropicClosurePreparationReport BarotropicClosureLinearizationContext::Prepare( + const BarotropicClosureStateView &state, + const BarotropicClosureDependencies &dependencies ) { + MFEM_VERIFY(state.density.Size() == m_densitySize, "The supported closure density vector has the wrong size."); MFEM_VERIFY( - baseDensityTrue.Size() == m_f.densityFes->GetTrueVSize(), - "The closure base-density vector has the wrong size." + state.enthalpy.Size() == m_enthalpySize, "The supported closure enthalpy vector has the wrong size." + ); + MFEM_VERIFY( + state.displacement.Size() == m_displacementSize, + "The supported closure displacement vector has the wrong size." ); - MFEM_VERIFY( - baseEnthalpyTrue.Size() == m_f.enthalpyFes->GetTrueVSize(), - "The closure base-enthalpy vector has the wrong size." - ); + validate_finite_vector(state.density, "The closure density state contains a non-finite value."); + validate_finite_vector(state.enthalpy, "The closure enthalpy state contains a non-finite value."); + validate_finite_vector(state.displacement, "The closure displacement state contains a non-finite value."); - MFEM_VERIFY( - displacementTrue.Size() == m_f.displacementFes->GetTrueVSize(), - "The closure displacement vector has the wrong size." - ); - - if (m_isPrepared && revisions == m_revisions) { - return; + if (m_isPrepared) { + validate_dependency_transition( + m_dependencies.discretization, dependencies.discretization, + "BarotropicClosureLinearizationContext received an older discretization revision for the same identity." + ); + validate_dependency_transition( + m_dependencies.density, dependencies.density, + "BarotropicClosureLinearizationContext received an older density revision for the same identity." + ); + validate_dependency_transition( + m_dependencies.enthalpy, dependencies.enthalpy, + "BarotropicClosureLinearizationContext received an older enthalpy revision for the same identity." + ); + validate_dependency_transition( + m_dependencies.displacement, dependencies.displacement, + "BarotropicClosureLinearizationContext received an older displacement revision for the same identity." + ); } - m_operator.Prepare(baseDensityTrue, baseEnthalpyTrue, displacementTrue); + const bool staticChanged = !m_isPrepared || dependencies.discretization != m_dependencies.discretization; + const bool densityChanged = !m_isPrepared || dependencies.density != m_dependencies.density; + const bool enthalpyChanged = !m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy; + const bool displacementChanged = !m_isPrepared || dependencies.displacement != m_dependencies.displacement; - m_baseDensityTrue = baseDensityTrue; - m_baseEnthalpyTrue = baseEnthalpyTrue; - m_displacementTrue = displacementTrue; + const bool geometryPreparationRequired = staticChanged || displacementChanged; + const bool baseStatePreparationRequired = + staticChanged || geometryPreparationRequired || densityChanged || enthalpyChanged; - m_revisions = revisions; - m_isPrepared = true; - ++m_preparationCount; + BarotropicClosurePreparationReport report; + report.preparedStaticDependencies = staticChanged; + report.preparedGeometryState = geometryPreparationRequired; + report.preparedBaseState = baseStatePreparationRequired; + + if (staticChanged || densityChanged) { + m_baseDensity = state.density; + report.updatedDensity = true; + } + if (staticChanged || enthalpyChanged) { + m_baseEnthalpy = state.enthalpy; + report.updatedEnthalpy = true; + } + if (geometryPreparationRequired) { + m_displacement = state.displacement; + report.updatedDisplacement = true; + } + + if (report.preparedStaticDependencies) { + ++m_statistics.staticPreparations; + } + if (report.preparedGeometryState) { + ++m_statistics.geometryPreparations; + } + if (report.preparedBaseState) { + ++m_statistics.baseStatePreparations; + } + + m_dependencies = dependencies; + m_isPrepared = true; + return report; } bool BarotropicClosureLinearizationContext::IsPrepared() const noexcept { return m_isPrepared; } - bool BarotropicClosureLinearizationContext::MatchesRevisions( - const BarotropicClosureRevisions &revisions + bool BarotropicClosureLinearizationContext::MatchesDependencies( + const BarotropicClosureDependencies &dependencies ) const noexcept { - return m_isPrepared && revisions == m_revisions; + return m_isPrepared && dependencies == m_dependencies; } - std::uint64_t BarotropicClosureLinearizationContext:: - GetPreparationCount() const noexcept { - return m_preparationCount; - } - - const BarotropicClosureRevisions & - BarotropicClosureLinearizationContext::GetRevisions() const { + const BarotropicClosureDependencies &BarotropicClosureLinearizationContext::GetDependencies() const { VerifyPrepared(); - return m_revisions; + return m_dependencies; } - const mfem::Vector & - BarotropicClosureLinearizationContext::GetBaseDensityTrue() const { + const BarotropicClosurePreparationStatistics & + BarotropicClosureLinearizationContext::GetPreparationStatistics() const noexcept { + return m_statistics; + } + + const mfem::Vector &BarotropicClosureLinearizationContext::GetBaseDensity() const { VerifyPrepared(); - return m_baseDensityTrue; + return m_baseDensity; } - const mfem::Vector & - BarotropicClosureLinearizationContext::GetBaseEnthalpyTrue() const { + const mfem::Vector &BarotropicClosureLinearizationContext::GetBaseEnthalpy() const { VerifyPrepared(); - return m_baseEnthalpyTrue; + return m_baseEnthalpy; } - const mfem::Vector & - BarotropicClosureLinearizationContext::GetDisplacementTrue() const { + const mfem::Vector &BarotropicClosureLinearizationContext::GetDisplacement() const { VerifyPrepared(); - return m_displacementTrue; - } - - const PreparedBarotropicClosureOperator & - BarotropicClosureLinearizationContext::GetOperator() const noexcept { - return m_operator; - } - - void BarotropicClosureLinearizationContext::BuildResidual( - mfem::Vector &residual - ) const { - VerifyPrepared(); - m_operator.BuildResidual(residual); + return m_displacement; } void BarotropicClosureLinearizationContext::VerifyPrepared() const { - MFEM_VERIFY( - m_isPrepared, "The barotropic-closure linearization context " - "has not been prepared." - ); + MFEM_VERIFY(m_isPrepared, "BarotropicClosureLinearizationContext has not been prepared."); } -} // namespace mean_field::operators::context::barotropic \ No newline at end of file +} // namespace mean_field::operators::context::barotropic diff --git a/libmeanfield/impl/operators/contexts/gravity_field_context.cpp b/libmeanfield/impl/operators/contexts/gravity_field_context.cpp index 07ee7b6..da18b40 100644 --- a/libmeanfield/impl/operators/contexts/gravity_field_context.cpp +++ b/libmeanfield/impl/operators/contexts/gravity_field_context.cpp @@ -12,9 +12,8 @@ namespace { const mfem::Vector &displacement_true ) { MFEM_VERIFY( - f.displacementFes != nullptr, - "GravityFieldGeometryContext requires the " - "displacement finite-element space." + f.displacementFes != nullptr, "GravityFieldGeometryContext requires the " + "displacement finite-element space." ); MFEM_VERIFY( displacement_true.Size() == f.displacementFes->GetTrueVSize(), @@ -25,18 +24,16 @@ namespace { for (int i = 0; i < displacement_true.Size(); ++i) { MFEM_VERIFY( - std::isfinite(displacement_true(i)), - "GravityFieldGeometryContext received a non-finite " - "displacement " - "value." + std::isfinite(displacement_true(i)), "GravityFieldGeometryContext received a non-finite " + "displacement " + "value." ); } } void validate_linearization_state( const mean_field::fem::FEM &f, - const mean_field::operators::context::gravity_field:: - GravityFieldStateView &state + const mean_field::operators::context::gravity_field::GravityFieldStateView &state ) { MFEM_VERIFY( f.densityFes != nullptr, "GravityFieldLinearizationContext " @@ -44,19 +41,16 @@ namespace { "space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "GravityFieldLinearizationContext requires the gravity-potential " - "finite-element space." + f.gravityPotentialFes != nullptr, "GravityFieldLinearizationContext requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "GravityFieldLinearizationContext requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "GravityFieldLinearizationContext requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "GravityFieldLinearizationContext requires " - "the displacement finite-element space." + f.displacementFes != nullptr, "GravityFieldLinearizationContext requires " + "the displacement finite-element space." ); MFEM_VERIFY( @@ -78,8 +72,7 @@ namespace { "with the wrong size." ); MFEM_VERIFY( - state.gravity_potential.Size() == - f.gravityPotentialFes->GetTrueVSize(), + state.gravity_potential.Size() == f.gravityPotentialFes->GetTrueVSize(), "GravityFieldLinearizationContext received a gravity-potential " "vector " "with the wrong size." @@ -87,35 +80,31 @@ namespace { for (int i = 0; i < state.density.Size(); ++i) { MFEM_VERIFY( - std::isfinite(state.density(i)), - "GravityFieldLinearizationContext received a non-finite " - "density " - "value." + std::isfinite(state.density(i)), "GravityFieldLinearizationContext received a non-finite " + "density " + "value." ); } for (int i = 0; i < state.displacement.Size(); ++i) { MFEM_VERIFY( - std::isfinite(state.displacement(i)), - "GravityFieldLinearizationContext received a non-finite " - "displacement " - "value." + std::isfinite(state.displacement(i)), "GravityFieldLinearizationContext received a non-finite " + "displacement " + "value." ); } for (int i = 0; i < state.gravity_gradient.Size(); ++i) { MFEM_VERIFY( - std::isfinite(state.gravity_gradient(i)), - "GravityFieldLinearizationContext received a non-finite " - "gravity-gradient value." + std::isfinite(state.gravity_gradient(i)), "GravityFieldLinearizationContext received a non-finite " + "gravity-gradient value." ); } for (int i = 0; i < state.gravity_potential.Size(); ++i) { MFEM_VERIFY( - std::isfinite(state.gravity_potential(i)), - "GravityFieldLinearizationContext received a non-finite " - "gravity-potential value." + std::isfinite(state.gravity_potential(i)), "GravityFieldLinearizationContext received a non-finite " + "gravity-potential value." ); } } @@ -128,42 +117,33 @@ namespace mean_field::operators::context::gravity_field { ) : m_fem(f), m_domain_mapper(domain_mapper) { + MFEM_VERIFY(f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh." + f.gravityFluxFes != nullptr, "GravityFieldGeometryContext requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "GravityFieldGeometryContext requires the " - "gravity-gradient finite-element space." + f.densityFes != nullptr, "GravityFieldGeometryContext requires the density finite-element " + "space." ); MFEM_VERIFY( - f.densityFes != nullptr, - "GravityFieldGeometryContext requires the density finite-element " - "space." + f.gravityPotentialFes != nullptr, "GravityFieldGeometryContext requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "GravityFieldGeometryContext requires the gravity-potential " - "finite-element space." + f.displacementFes != nullptr, "GravityFieldGeometryContext requires the " + "displacement finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "GravityFieldGeometryContext requires the " - "displacement finite-element space." + f.compactificationFes != nullptr, "GravityFieldGeometryContext requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "GravityFieldGeometryContext requires the compactification " - "finite-element space." + f.compactificationCoordinate != nullptr, "GravityFieldGeometryContext requires the compactification " + "coordinate." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "GravityFieldGeometryContext requires the compactification " - "coordinate." - ); - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "GravityFieldGeometryContext requires the quadrature-rule factory." + f.quadratureFactory != nullptr, "GravityFieldGeometryContext requires the quadrature-rule factory." ); MFEM_VERIFY( domain_mapper.GetDimension() == f.mesh->Dimension(), @@ -192,11 +172,8 @@ namespace mean_field::operators::context::gravity_field { ); } - const bool discretization_changed = - !m_is_prepared || - discretization_revision != m_discretization_revision; - const bool displacement_changed = - !m_is_prepared || displacement_revision != m_displacement_revision; + const bool discretization_changed = !m_is_prepared || discretization_revision != m_discretization_revision; + const bool displacement_changed = !m_is_prepared || displacement_revision != m_displacement_revision; GravityFieldGeometryPreparation preparation; @@ -205,14 +182,8 @@ namespace mean_field::operators::context::gravity_field { } if (discretization_changed) { - auto mass_operator = - std::make_unique( - m_fem, m_domain_mapper - ); - auto source_operator = - std::make_unique( - m_fem, m_domain_mapper - ); + auto mass_operator = std::make_unique(m_fem, m_domain_mapper); + auto source_operator = std::make_unique(m_fem, m_domain_mapper); mass_operator->Prepare(displacement_true); source_operator->Prepare(displacement_true); @@ -229,9 +200,8 @@ namespace mean_field::operators::context::gravity_field { "no prepared H(div) mass operator." ); MFEM_VERIFY( - m_source_operator != nullptr, - "GravityFieldGeometryContext has no prepared gravity source " - "operator." + m_source_operator != nullptr, "GravityFieldGeometryContext has no prepared gravity source " + "operator." ); m_mass_operator->Prepare(displacement_true); @@ -251,26 +221,19 @@ namespace mean_field::operators::context::gravity_field { return preparation; } - const PreparedMappedHDivMassOperator & - GravityFieldGeometryContext::GetMassOperator() const { + const PreparedMappedHDivMassOperator &GravityFieldGeometryContext::GetMassOperator() const { MFEM_VERIFY( - m_is_prepared, - "GravityFieldGeometryContext must be prepared before " - "accessing its mass operator." - ); - MFEM_VERIFY( - m_mass_operator != nullptr, - "GravityFieldGeometryContext has no prepared H(div) mass operator." + m_is_prepared, "GravityFieldGeometryContext must be prepared before " + "accessing its mass operator." ); + MFEM_VERIFY(m_mass_operator != nullptr, "GravityFieldGeometryContext has no prepared H(div) mass operator."); return *m_mass_operator; } - const PreparedMappedGravitySourceOperator & - GravityFieldGeometryContext::GetSourceOperator() const { + const PreparedMappedGravitySourceOperator &GravityFieldGeometryContext::GetSourceOperator() const { MFEM_VERIFY( - m_is_prepared, - "GravityFieldGeometryContext must be prepared before " - "accessing its source operator." + m_is_prepared, "GravityFieldGeometryContext must be prepared before " + "accessing its source operator." ); MFEM_VERIFY( m_source_operator != nullptr, "GravityFieldGeometryContext has no " @@ -281,20 +244,17 @@ namespace mean_field::operators::context::gravity_field { const mfem::Vector &GravityFieldGeometryContext::GetDisplacement() const { MFEM_VERIFY( - m_is_prepared, - "GravityFieldGeometryContext must be prepared before " - "accessing its displacement." + m_is_prepared, "GravityFieldGeometryContext must be prepared before " + "accessing its displacement." ); return m_displacement_true; } - DiscretizationRevision - GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept { + DiscretizationRevision GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept { return m_discretization_revision; } - DisplacementRevision - GravityFieldGeometryContext::GetDisplacementRevision() const noexcept { + DisplacementRevision GravityFieldGeometryContext::GetDisplacementRevision() const noexcept { return m_displacement_revision; } @@ -317,19 +277,16 @@ namespace mean_field::operators::context::gravity_field { "space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "GravityFieldLinearizationContext requires the gravity-potential " - "finite-element space." + f.gravityPotentialFes != nullptr, "GravityFieldLinearizationContext requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "GravityFieldLinearizationContext requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "GravityFieldLinearizationContext requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "GravityFieldLinearizationContext requires " - "the displacement finite-element space." + f.displacementFes != nullptr, "GravityFieldLinearizationContext requires " + "the displacement finite-element space." ); } @@ -353,9 +310,8 @@ namespace mean_field::operators::context::gravity_field { "revision." ); MFEM_VERIFY( - revisions.density >= m_revisions.density, - "GravityFieldLinearizationContext received an older density " - "revision." + revisions.density >= m_revisions.density, "GravityFieldLinearizationContext received an older density " + "revision." ); MFEM_VERIFY( revisions.gravity_gradient >= m_revisions.gravity_gradient, @@ -370,20 +326,16 @@ namespace mean_field::operators::context::gravity_field { ); } - const bool discretization_changed = - !m_is_prepared || - revisions.discretization != m_revisions.discretization; - const bool density_changed = !m_is_prepared || discretization_changed || - revisions.density != m_revisions.density; + const bool discretization_changed = !m_is_prepared || revisions.discretization != m_revisions.discretization; + const bool density_changed = + !m_is_prepared || discretization_changed || revisions.density != m_revisions.density; const bool gravity_gradient_changed = - !m_is_prepared || discretization_changed || - revisions.gravity_gradient != m_revisions.gravity_gradient; + !m_is_prepared || discretization_changed || revisions.gravity_gradient != m_revisions.gravity_gradient; GravityFieldPreparationReport report; - report.geometry = m_geometry_context.Prepare( - state.displacement, revisions.discretization, revisions.displacement - ); + report.geometry = + m_geometry_context.Prepare(state.displacement, revisions.discretization, revisions.displacement); if (density_changed) { m_density_true = state.density; @@ -401,8 +353,7 @@ namespace mean_field::operators::context::gravity_field { return report; } - const GravityFieldGeometryContext & - GravityFieldLinearizationContext::GetGeometryContext() const { + const GravityFieldGeometryContext &GravityFieldLinearizationContext::GetGeometryContext() const { MFEM_VERIFY( m_is_prepared, "GravityFieldLinearizationContext must be prepared " "before accessing its geometry context." @@ -418,8 +369,7 @@ namespace mean_field::operators::context::gravity_field { return m_density_true; } - const mfem::Vector & - GravityFieldLinearizationContext::GetGravityGradient() const { + const mfem::Vector &GravityFieldLinearizationContext::GetGravityGradient() const { MFEM_VERIFY( m_is_prepared, "GravityFieldLinearizationContext must be prepared " "before accessing its gravity gradient." @@ -427,8 +377,7 @@ namespace mean_field::operators::context::gravity_field { return m_gravity_gradient_true; } - const GravityFieldRevisions & - GravityFieldLinearizationContext::GetRevisions() const { + const GravityFieldRevisions &GravityFieldLinearizationContext::GetRevisions() const { MFEM_VERIFY( m_is_prepared, "GravityFieldLinearizationContext must be prepared " "before accessing its revisions." diff --git a/libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp b/libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp index 67886ef..55ac617 100644 --- a/libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp +++ b/libmeanfield/impl/operators/contexts/hydrostatic_equilibrium_context.cpp @@ -20,44 +20,37 @@ namespace { void validate_state( const mean_field::fem::FEM &f, - const mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumStateView &state + const mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView &state ) { MFEM_VERIFY( - f.enthalpyFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "enthalpy finite-element space." + f.enthalpyFes != nullptr, "HydrostaticEquilibriumContext requires the " + "enthalpy finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "gravity-potential finite-element space." + f.gravityPotentialFes != nullptr, "HydrostaticEquilibriumContext requires the " + "gravity-potential finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "displacement finite-element space." + f.displacementFes != nullptr, "HydrostaticEquilibriumContext requires the " + "displacement finite-element space." ); MFEM_VERIFY( - state.enthalpy.Size() == f.enthalpyFes->GetTrueVSize(), - "HydrostaticEquilibriumContext received an " - "enthalpy vector with the wrong size." + state.enthalpy.Size() == f.enthalpyFes->GetTrueVSize(), "HydrostaticEquilibriumContext received an " + "enthalpy vector with the wrong size." ); MFEM_VERIFY( - state.gravityPotential.Size() == - f.gravityPotentialFes->GetTrueVSize(), + state.gravityPotential.Size() == f.gravityPotentialFes->GetTrueVSize(), "HydrostaticEquilibriumContext received a " "gravity-potential vector with the wrong size." ); MFEM_VERIFY( - state.displacement.Size() == f.displacementFes->GetTrueVSize(), - "HydrostaticEquilibriumContext received a " - "displacement vector with the wrong size." + state.displacement.Size() == f.displacementFes->GetTrueVSize(), "HydrostaticEquilibriumContext received a " + "displacement vector with the wrong size." ); validate_finite_vector( @@ -76,9 +69,8 @@ namespace { ); MFEM_VERIFY( - std::isfinite(state.bernoulliConstant), - "HydrostaticEquilibriumContext received a " - "non-finite Bernoulli constant." + std::isfinite(state.bernoulliConstant), "HydrostaticEquilibriumContext received a " + "non-finite Bernoulli constant." ); } @@ -99,34 +91,27 @@ namespace mean_field::operators::context::hydrostatic { ) : m_f(f), m_domainMapper(domainMapper) { + MFEM_VERIFY(m_f.mesh != nullptr, "HydrostaticEquilibriumContext requires a mesh."); + MFEM_VERIFY( - m_f.mesh != nullptr, - "HydrostaticEquilibriumContext requires a mesh." + m_f.enthalpyFes != nullptr, "HydrostaticEquilibriumContext requires the " + "enthalpy finite-element space." ); MFEM_VERIFY( - m_f.enthalpyFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "enthalpy finite-element space." + m_f.gravityPotentialFes != nullptr, "HydrostaticEquilibriumContext requires the " + "gravity-potential finite-element space." ); MFEM_VERIFY( - m_f.gravityPotentialFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "gravity-potential finite-element space." + m_f.displacementFes != nullptr, "HydrostaticEquilibriumContext requires the " + "displacement finite-element space." ); MFEM_VERIFY( - m_f.displacementFes != nullptr, - "HydrostaticEquilibriumContext requires the " - "displacement finite-element space." - ); - - MFEM_VERIFY( - m_domainMapper.GetDimension() == m_f.mesh->Dimension(), - "The hydrostatic context's stateless " - "domain-mapper dimension does not match the mesh " - "dimension." + m_domainMapper.GetDimension() == m_f.mesh->Dimension(), "The hydrostatic context's stateless " + "domain-mapper dimension does not match the mesh " + "dimension." ); } @@ -168,44 +153,32 @@ namespace mean_field::operators::context::hydrostatic { ); validate_dependency_transition( - m_dependencies.bernoulliConstant, - dependencies.bernoulliConstant, + m_dependencies.bernoulliConstant, dependencies.bernoulliConstant, "HydrostaticEquilibriumContext received an older " "Bernoulli-constant revision for the same identity." ); } - const bool staticChanged = - !m_isPrepared || - dependencies.discretization != m_dependencies.discretization; + const bool staticChanged = !m_isPrepared || dependencies.discretization != m_dependencies.discretization; - const bool enthalpyChanged = - !m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy; + const bool enthalpyChanged = !m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy; const bool gravityPotentialChanged = - !m_isPrepared || - dependencies.gravityPotential != m_dependencies.gravityPotential; + !m_isPrepared || dependencies.gravityPotential != m_dependencies.gravityPotential; - const bool displacementChanged = - !m_isPrepared || - dependencies.displacement != m_dependencies.displacement; + const bool displacementChanged = !m_isPrepared || dependencies.displacement != m_dependencies.displacement; - const bool rotationChanged = - !m_isPrepared || dependencies.rotation != m_dependencies.rotation; + const bool rotationChanged = !m_isPrepared || dependencies.rotation != m_dependencies.rotation; const bool bernoulliConstantChanged = - !m_isPrepared || - dependencies.bernoulliConstant != m_dependencies.bernoulliConstant; + !m_isPrepared || dependencies.bernoulliConstant != m_dependencies.bernoulliConstant; - const bool geometryPreparationRequired = - staticChanged || displacementChanged; + const bool geometryPreparationRequired = staticChanged || displacementChanged; - const bool rotationPreparationRequired = - geometryPreparationRequired || rotationChanged; + const bool rotationPreparationRequired = geometryPreparationRequired || rotationChanged; const bool baseStatePreparationRequired = - rotationPreparationRequired || enthalpyChanged || - gravityPotentialChanged || bernoulliConstantChanged; + rotationPreparationRequired || enthalpyChanged || gravityPotentialChanged || bernoulliConstantChanged; HydrostaticPreparationReport report; @@ -267,31 +240,26 @@ namespace mean_field::operators::context::hydrostatic { return m_isPrepared && dependencies == m_dependencies; } - const HydrostaticEquilibriumDependencies & - HydrostaticEquilibriumContext::GetDependencies() const { + const HydrostaticEquilibriumDependencies &HydrostaticEquilibriumContext::GetDependencies() const { VerifyPrepared(); return m_dependencies; } - const HydrostaticPreparationStatistics & - HydrostaticEquilibriumContext::GetPreparationStatistics() const noexcept { + const HydrostaticPreparationStatistics &HydrostaticEquilibriumContext::GetPreparationStatistics() const noexcept { return m_statistics; } - const mfem::Vector & - HydrostaticEquilibriumContext::GetBaseEnthalpyTrue() const { + const mfem::Vector &HydrostaticEquilibriumContext::GetBaseEnthalpyTrue() const { VerifyPrepared(); return m_baseEnthalpyTrue; } - const mfem::Vector & - HydrostaticEquilibriumContext::GetBaseGravityPotentialTrue() const { + const mfem::Vector &HydrostaticEquilibriumContext::GetBaseGravityPotentialTrue() const { VerifyPrepared(); return m_baseGravityPotentialTrue; } - const mfem::Vector & - HydrostaticEquilibriumContext::GetDisplacementTrue() const { + const mfem::Vector &HydrostaticEquilibriumContext::GetDisplacementTrue() const { VerifyPrepared(); return m_displacementTrue; } @@ -302,8 +270,6 @@ namespace mean_field::operators::context::hydrostatic { } void HydrostaticEquilibriumContext::VerifyPrepared() const { - MFEM_VERIFY( - m_isPrepared, "HydrostaticEquilibriumContext has not been prepared." - ); + MFEM_VERIFY(m_isPrepared, "HydrostaticEquilibriumContext has not been prepared."); } } // namespace mean_field::operators::context::hydrostatic diff --git a/libmeanfield/impl/operators/contexts/pressure_force_context.cpp b/libmeanfield/impl/operators/contexts/pressure_force_context.cpp new file mode 100644 index 0000000..f5f1212 --- /dev/null +++ b/libmeanfield/impl/operators/contexts/pressure_force_context.cpp @@ -0,0 +1,219 @@ +module; + +#include + +#include + +module mean_field; + +import :operators.context.pressure_force; + +namespace { + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + template + void validate_dependency_transition( + const Dependency &prepared, + const Dependency &requested, + const char *message + ) { + MFEM_VERIFY(requested.CanFollow(prepared), message); + + MFEM_VERIFY( + prepared.identity == requested.identity || prepared.revision != requested.revision, + "A new pressure-force dependency identity must also carry " + "a visibly different revision." + ); + } +} // namespace + +namespace mean_field::operators::context::pressure_force { + PressureForceLinearizationContext::PressureForceLinearizationContext( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const field::FieldDofMap &enthalpyMap, + const field::FieldDofMap &displacementMap + ) + : m_enthalpySize(enthalpyMap.reduced_size()), + m_displacementSize(displacementMap.reduced_size()) { + MFEM_VERIFY(f.mesh != nullptr, "PressureForceLinearizationContext requires a mesh."); + + MFEM_VERIFY( + f.enthalpyFes != nullptr, "PressureForceLinearizationContext requires the enthalpy " + "finite-element space." + ); + + MFEM_VERIFY( + f.displacementFes != nullptr, "PressureForceLinearizationContext requires the displacement " + "finite-element space." + ); + + MFEM_VERIFY( + domainMapper.GetDimension() == f.mesh->Dimension(), "The pressure-force context's stateless domain-mapper " + "dimension does not match the mesh dimension." + ); + + MFEM_VERIFY( + enthalpyMap.full_size() == f.enthalpyFes->GetTrueVSize(), + "The pressure-force enthalpy FieldDofMap does not match the " + "enthalpy finite-element space." + ); + + MFEM_VERIFY( + displacementMap.full_size() == f.displacementFes->GetTrueVSize(), + "The pressure-force displacement FieldDofMap does not match " + "the displacement finite-element space." + ); + } + + PressureForcePreparationReport PressureForceLinearizationContext::Prepare( + const PressureForceStateView &state, + const PressureForceDependencies &dependencies + ) { + MFEM_VERIFY( + state.enthalpy.Size() == m_enthalpySize, "PressureForceLinearizationContext received a supported " + "enthalpy vector with the wrong size." + ); + + MFEM_VERIFY( + state.displacement.Size() == m_displacementSize, "PressureForceLinearizationContext received a supported " + "displacement vector with the wrong size." + ); + + validate_finite_vector( + state.enthalpy, "PressureForceLinearizationContext received a non-finite " + "enthalpy value." + ); + + validate_finite_vector( + state.displacement, "PressureForceLinearizationContext received a non-finite " + "displacement value." + ); + + if (m_isPrepared) { + validate_dependency_transition( + m_dependencies.discretization, dependencies.discretization, + "PressureForceLinearizationContext received an older " + "discretization revision for the same identity." + ); + + validate_dependency_transition( + m_dependencies.enthalpy, dependencies.enthalpy, + "PressureForceLinearizationContext received an older " + "enthalpy revision for the same identity." + ); + + validate_dependency_transition( + m_dependencies.displacement, dependencies.displacement, + "PressureForceLinearizationContext received an older " + "displacement revision for the same identity." + ); + } + + const bool discretizationChanged = + !m_isPrepared || dependencies.discretization != m_dependencies.discretization; + + const bool enthalpyChanged = !m_isPrepared || dependencies.enthalpy != m_dependencies.enthalpy; + + const bool displacementChanged = !m_isPrepared || dependencies.displacement != m_dependencies.displacement; + + /* + * Static data depend only on discretization. + * + * Geometry data depend on discretization and displacement. + * + * Material data depend on both geometry and enthalpy because + * pressure and its enthalpy derivative are evaluated on the frozen + * mapped state. + */ + const bool geometryPreparationRequired = discretizationChanged || displacementChanged; + + const bool materialPreparationRequired = geometryPreparationRequired || enthalpyChanged; + + PressureForcePreparationReport report; + + report.preparedStaticDependencies = discretizationChanged; + + report.preparedGeometryState = geometryPreparationRequired; + + report.preparedMaterialState = materialPreparationRequired; + + /* + * A discretization change invalidates every frozen field because + * their coordinate interpretation may have changed. + */ + if (discretizationChanged || enthalpyChanged) { + m_baseEnthalpy = state.enthalpy; + + report.updatedEnthalpy = true; + } + + if (geometryPreparationRequired) { + m_displacement = state.displacement; + + report.updatedDisplacement = true; + } + + if (report.preparedStaticDependencies) { + ++m_statistics.staticPreparations; + } + + if (report.preparedGeometryState) { + ++m_statistics.geometryPreparations; + } + + if (report.preparedMaterialState) { + ++m_statistics.materialPreparations; + } + + m_dependencies = dependencies; + + m_isPrepared = true; + + return report; + } + + const PressureForcePreparationStatistics & + PressureForceLinearizationContext::GetPreparationStatistics() const noexcept { + return m_statistics; + } + + bool PressureForceLinearizationContext::IsPrepared() const noexcept { + return m_isPrepared; + } + + bool PressureForceLinearizationContext::MatchesDependencies( + const PressureForceDependencies &dependencies + ) const noexcept { + return m_isPrepared && dependencies == m_dependencies; + } + + const PressureForceDependencies &PressureForceLinearizationContext::GetDependencies() const { + VerifyPrepared(); + + return m_dependencies; + } + + const mfem::Vector &PressureForceLinearizationContext::GetBaseEnthalpy() const { + VerifyPrepared(); + + return m_baseEnthalpy; + } + + const mfem::Vector &PressureForceLinearizationContext::GetDisplacement() const { + VerifyPrepared(); + + return m_displacement; + } + + void PressureForceLinearizationContext::VerifyPrepared() const { + MFEM_VERIFY(m_isPrepared, "PressureForceLinearizationContext has not been prepared."); + } +} // namespace mean_field::operators::context::pressure_force \ No newline at end of file diff --git a/libmeanfield/impl/operators/contexts/rotation_displacement_force_context.cpp b/libmeanfield/impl/operators/contexts/rotation_displacement_force_context.cpp new file mode 100644 index 0000000..43187e2 --- /dev/null +++ b/libmeanfield/impl/operators/contexts/rotation_displacement_force_context.cpp @@ -0,0 +1,203 @@ +module; + +#include + +#include + +module mean_field; + +import :operators.context.rotational_displacement_force; + +namespace { + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + template + void validate_dependency_transition( + const Dependency &prepared, + const Dependency &requested, + const char *message + ) { + MFEM_VERIFY(requested.CanFollow(prepared), message); + } +} // namespace + +namespace mean_field::operators::context::rotational_displacement_force { + RotationalDisplacementForceLinearizationContext::RotationalDisplacementForceLinearizationContext( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper + ) + : m_f(f) { + MFEM_VERIFY( + m_f.mesh != nullptr, "RotationalDisplacementForceLinearizationContext requires a " + "mesh." + ); + + MFEM_VERIFY( + m_f.densityFes != nullptr, "RotationalDisplacementForceLinearizationContext requires the " + "density finite-element space." + ); + + MFEM_VERIFY( + m_f.displacementFes != nullptr, "RotationalDisplacementForceLinearizationContext requires the " + "displacement finite-element space." + ); + + MFEM_VERIFY( + domainMapper.GetDimension() == m_f.mesh->Dimension(), + "The rotational-displacement-force context's stateless " + "domain-mapper dimension does not match the mesh dimension." + ); + } + + RotationalDisplacementForcePreparationReport RotationalDisplacementForceLinearizationContext::Prepare( + const RotationalDisplacementForceStateView &state, + const RotationalDisplacementForceDependencies &dependencies + ) { + MFEM_VERIFY( + state.density.Size() == m_f.densityFes->GetTrueVSize(), + "RotationalDisplacementForceLinearizationContext received a " + "density vector with the wrong size." + ); + + MFEM_VERIFY( + state.displacement.Size() == m_f.displacementFes->GetTrueVSize(), + "RotationalDisplacementForceLinearizationContext received a " + "displacement vector with the wrong size." + ); + + validate_finite_vector( + state.density, "RotationalDisplacementForceLinearizationContext received a " + "non-finite density value." + ); + + validate_finite_vector( + state.displacement, "RotationalDisplacementForceLinearizationContext received a " + "non-finite displacement value." + ); + + if (m_isPrepared) { + validate_dependency_transition( + m_dependencies.discretization, dependencies.discretization, + "RotationalDisplacementForceLinearizationContext received " + "an older discretization revision for the same identity." + ); + + validate_dependency_transition( + m_dependencies.density, dependencies.density, + "RotationalDisplacementForceLinearizationContext received " + "an older density revision for the same identity." + ); + + validate_dependency_transition( + m_dependencies.displacement, dependencies.displacement, + "RotationalDisplacementForceLinearizationContext received " + "an older displacement revision for the same identity." + ); + + validate_dependency_transition( + m_dependencies.rotation, dependencies.rotation, + "RotationalDisplacementForceLinearizationContext received " + "an older rotation revision for the same identity." + ); + } + + const bool discretizationChanged = + !m_isPrepared || dependencies.discretization != m_dependencies.discretization; + + const bool densityChanged = !m_isPrepared || dependencies.density != m_dependencies.density; + + const bool displacementChanged = !m_isPrepared || dependencies.displacement != m_dependencies.displacement; + + const bool rotationChanged = !m_isPrepared || dependencies.rotation != m_dependencies.rotation; + + const bool geometryPreparationRequired = discretizationChanged || displacementChanged; + + const bool rotationPreparationRequired = discretizationChanged || rotationChanged; + + const bool baseStatePreparationRequired = + geometryPreparationRequired || rotationPreparationRequired || densityChanged; + + RotationalDisplacementForcePreparationReport report; + + report.preparedStaticDependencies = discretizationChanged; + report.preparedGeometryState = geometryPreparationRequired; + report.preparedRotationDependencies = rotationPreparationRequired; + report.preparedBaseState = baseStatePreparationRequired; + + if (discretizationChanged || densityChanged) { + m_baseDensityTrue = state.density; + report.updatedDensity = true; + } + + if (geometryPreparationRequired) { + m_displacementTrue = state.displacement; + report.updatedDisplacement = true; + } + + if (report.preparedStaticDependencies) { + ++m_statistics.staticPreparations; + } + + if (report.preparedGeometryState) { + ++m_statistics.geometryPreparations; + } + + if (report.preparedRotationDependencies) { + ++m_statistics.rotationPreparations; + } + + if (report.preparedBaseState) { + ++m_statistics.baseStatePreparations; + } + + m_dependencies = dependencies; + m_isPrepared = true; + + return report; + } + + bool RotationalDisplacementForceLinearizationContext::IsPrepared() const noexcept { + return m_isPrepared; + } + + bool RotationalDisplacementForceLinearizationContext::MatchesDependencies( + const RotationalDisplacementForceDependencies &dependencies + ) const noexcept { + return m_isPrepared && dependencies == m_dependencies; + } + + const RotationalDisplacementForceDependencies & + RotationalDisplacementForceLinearizationContext::GetDependencies() const { + VerifyPrepared(); + return m_dependencies; + } + + const RotationalDisplacementForcePreparationStatistics & + RotationalDisplacementForceLinearizationContext::GetPreparationStatistics() const noexcept { + return m_statistics; + } + + const mfem::Vector &RotationalDisplacementForceLinearizationContext::GetBaseDensityTrue() const { + VerifyPrepared(); + return m_baseDensityTrue; + } + + const mfem::Vector &RotationalDisplacementForceLinearizationContext::GetDisplacementTrue() const { + VerifyPrepared(); + return m_displacementTrue; + } + + void RotationalDisplacementForceLinearizationContext::VerifyPrepared() const { + MFEM_VERIFY( + m_isPrepared, "RotationalDisplacementForceLinearizationContext has not been " + "prepared." + ); + } +} // namespace mean_field::operators::context::rotational_displacement_force diff --git a/libmeanfield/impl/operators/gravity_field.cpp b/libmeanfield/impl/operators/gravity_field.cpp index e8be7a8..62dfa34 100644 --- a/libmeanfield/impl/operators/gravity_field.cpp +++ b/libmeanfield/impl/operators/gravity_field.cpp @@ -13,43 +13,31 @@ import :operators.kernels.gravity_field; namespace { using namespace mean_field; int get_state_width(const mfem::Array &state_true_offsets) { - MFEM_VERIFY( - state_true_offsets.Size() >= 2, - "The coupled state requires at least one block." - ); - MFEM_VERIFY( - state_true_offsets[0] == 0, - "The coupled state offsets must begin at zero." - ); + MFEM_VERIFY(state_true_offsets.Size() >= 2, "The coupled state requires at least one block."); + MFEM_VERIFY(state_true_offsets[0] == 0, "The coupled state offsets must begin at zero."); for (int i = 0; i < state_true_offsets.Size() - 1; ++i) { MFEM_VERIFY( - state_true_offsets[i + 1] >= state_true_offsets[i], - "The coupled state offsets must be nondecreasing." + state_true_offsets[i + 1] >= state_true_offsets[i], "The coupled state offsets must be nondecreasing." ); } - MFEM_VERIFY( - state_true_offsets.Last() > 0, "The coupled state cannot be empty." - ); + MFEM_VERIFY(state_true_offsets.Last() > 0, "The coupled state cannot be empty."); return state_true_offsets.Last(); } int get_gravity_residual_height(const fem::FEM &f) { MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "GravityFieldOperator requires the gravity-gradient finite-element " - "space (RT: Raviart-Thomas)." + f.gravityFluxFes != nullptr, "GravityFieldOperator requires the gravity-gradient finite-element " + "space (RT: Raviart-Thomas)." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "GravityFieldOperator requires the gravity-potential " - "finite-element " - "space (L2: Lebesgue " - "space of square-integrable functions)." + f.gravityPotentialFes != nullptr, "GravityFieldOperator requires the gravity-potential " + "finite-element " + "space (L2: Lebesgue " + "space of square-integrable functions)." ); - return f.gravityFluxFes->GetTrueVSize() + - f.gravityPotentialFes->GetTrueVSize(); + return f.gravityFluxFes->GetTrueVSize() + f.gravityPotentialFes->GetTrueVSize(); } mfem::Array make_gravity_residual_offsets(const fem::FEM &f) { @@ -65,10 +53,7 @@ namespace { const mfem::Array &state_true_offsets, const utils::blocks::value_block ) { - MFEM_VERIFY( - index + 1 < state_true_offsets.Size(), - "Value block is not present in the state offsets." - ); + MFEM_VERIFY(index + 1 < state_true_offsets.Size(), "Value block is not present in the state offsets."); return state_true_offsets[index + 1] - state_true_offsets[index]; } @@ -76,10 +61,7 @@ namespace { const fem::FEM &f, const mfem::Array &state_true_offsets ) { - MFEM_VERIFY( - f.densityFes != nullptr, - "GravityFieldOperator requires the density finite-element space." - ); + MFEM_VERIFY(f.densityFes != nullptr, "GravityFieldOperator requires the density finite-element space."); MFEM_VERIFY( f.displacementFes != nullptr, "GravityFieldOperator requires the " "displacement finite-element space." @@ -87,65 +69,44 @@ namespace { using form = utils::blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block
( - utils::blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); constexpr auto displacement_block = - utils::blocks::get_value_block( - utils::blocks::displacement_field.geometry_term - ); + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); MFEM_VERIFY( state_true_offsets.Size() == form::value_block_count + 1, "The gravity state offsets do not match gravity_field_form." ); MFEM_VERIFY( - get_state_block_size(state_true_offsets, density_block) == - f.densityFes->GetTrueVSize(), + get_state_block_size(state_true_offsets, density_block) == f.densityFes->GetTrueVSize(), "The density block does not match the density finite-element space." ); MFEM_VERIFY( - get_state_block_size(state_true_offsets, displacement_block) == - f.displacementFes->GetTrueVSize(), + get_state_block_size(state_true_offsets, displacement_block) == f.displacementFes->GetTrueVSize(), "The displacement block does not match the displacement " "finite-element " "space." ); MFEM_VERIFY( - get_state_block_size(state_true_offsets, gravity_gradient_block) == - f.gravityFluxFes->GetTrueVSize(), + get_state_block_size(state_true_offsets, gravity_gradient_block) == f.gravityFluxFes->GetTrueVSize(), "The gravity-gradient block does not match the RT finite-element " "space." ); MFEM_VERIFY( - get_state_block_size(state_true_offsets, gravity_potential_block) == - f.gravityPotentialFes->GetTrueVSize(), + get_state_block_size(state_true_offsets, gravity_potential_block) == f.gravityPotentialFes->GetTrueVSize(), "The gravity-potential block does not match the potential " "finite-element space." ); } void validate_gravity_context(const fem::FEM &f) { - MFEM_VERIFY( - f.gravityContext.b_form != nullptr, - "GravityFieldOperator requires the divergence operator." - ); - MFEM_VERIFY( - f.gravityContext.BT != nullptr, - "GravityFieldOperator requires the transpose divergence operator." - ); - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "GravityFieldOperator requires the quadrature-rule factory." - ); + MFEM_VERIFY(f.gravityContext.b_form != nullptr, "GravityFieldOperator requires the divergence operator."); + MFEM_VERIFY(f.gravityContext.BT != nullptr, "GravityFieldOperator requires the transpose divergence operator."); + MFEM_VERIFY(f.quadratureFactory != nullptr, "GravityFieldOperator requires the quadrature-rule factory."); } template @@ -154,21 +115,13 @@ namespace { const mfem::Array &offsets, const utils::blocks::value_block ) { - MFEM_VERIFY( - index + 1 < offsets.Size(), - "Value block is not present in the supplied offset array." - ); + MFEM_VERIFY(index + 1 < offsets.Size(), "Value block is not present in the supplied offset array."); const int begin = offsets[index]; const int size = offsets[index + 1] - begin; - MFEM_VERIFY( - vector.Size() == offsets.Last(), - "Vector size does not match the value-block offsets." - ); - return mfem::Vector( - const_cast(vector.GetData()) + begin, size - ); + MFEM_VERIFY(vector.Size() == offsets.Last(), "Vector size does not match the value-block offsets."); + return mfem::Vector(const_cast(vector.GetData()) + begin, size); } template @@ -181,10 +134,7 @@ namespace { const int begin = offsets[block_id]; const int size = offsets[block_id + 1] - begin; - MFEM_VERIFY( - vector.Size() == offsets.Last(), - "The vector does not match the residual-block layout." - ); + MFEM_VERIFY(vector.Size() == offsets.Last(), "The vector does not match the residual-block layout."); mfem::Vector view; view.MakeRef(const_cast(vector), begin, size); @@ -197,18 +147,12 @@ namespace { const mfem::Array &offsets, const utils::blocks::residual_block ) { - MFEM_VERIFY( - index + 1 < offsets.Size(), - "Residual block is not present in the supplied offset array." - ); + MFEM_VERIFY(index + 1 < offsets.Size(), "Residual block is not present in the supplied offset array."); const int begin = offsets[index]; const int size = offsets[index + 1] - begin; - MFEM_VERIFY( - vector.Size() == offsets.Last(), - "Vector size does not match the residual-block offsets." - ); + MFEM_VERIFY(vector.Size() == offsets.Last(), "Vector size does not match the residual-block offsets."); return mfem::Vector(vector.GetData() + begin, size); } } // namespace @@ -217,8 +161,7 @@ namespace mean_field::operators { GravityFieldOperator::GravityFieldOperator( fem::FEM &f, const mapping::DomainMapperStateless &domain_mapper, - context::gravity_field::GravityFieldLinearizationContext - &linearization_context, + context::gravity_field::GravityFieldLinearizationContext &linearization_context, const mfem::Array &state_true_offsets, GravityFieldJacobianOperator &jacobian ) @@ -238,14 +181,12 @@ namespace mean_field::operators { "displacement finite-element space." ); MFEM_VERIFY( - f.smesh.exterior_coordinate != nullptr, - "GravityFieldOperator requires the STROID exterior coordinate." + f.smesh.exterior_coordinate != nullptr, "GravityFieldOperator requires the STROID exterior coordinate." ); MFEM_VERIFY( - f.smesh.exterior_coordinate->space != nullptr, - "GravityFieldOperator requires the exterior-coordinate " - "finite-element " - "space." + f.smesh.exterior_coordinate->space != nullptr, "GravityFieldOperator requires the exterior-coordinate " + "finite-element " + "space." ); MFEM_VERIFY( f.smesh.exterior_coordinate->values != nullptr, @@ -263,67 +204,46 @@ namespace mean_field::operators { bool has_vacuum_domain = false; for (int i = 0; i < f.mesh->attributes.Size(); ++i) { - if (f.mesh->attributes[i] == - domain_mapper.GetVacuumElementAttribute()) { + if (f.mesh->attributes[i] == domain_mapper.GetVacuumElementAttribute()) { has_vacuum_domain = true; break; } } + MFEM_VERIFY(has_vacuum_domain, "GravityFieldOperator requires a compactified vacuum domain."); MFEM_VERIFY( - has_vacuum_domain, - "GravityFieldOperator requires a compactified vacuum domain." + m_residual_true_offsets.Last() == Height(), "The gravity residual offsets do not match the operator height." ); MFEM_VERIFY( - m_residual_true_offsets.Last() == Height(), - "The gravity residual offsets do not match the operator height." - ); - MFEM_VERIFY( - m_state_true_offsets.Last() == Width(), - "The coupled state offsets do not match the operator width." + m_state_true_offsets.Last() == Width(), "The coupled state offsets do not match the operator width." ); } - context::gravity_field::GravityFieldPreparationReport - GravityFieldOperator::Prepare( + context::gravity_field::GravityFieldPreparationReport GravityFieldOperator::Prepare( const mfem::Vector &state, const context::gravity_field::GravityFieldRevisions &revisions ) { using form = utils::blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block( - utils::blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); constexpr auto displacement_block = - utils::blocks::get_value_block( - utils::blocks::displacement_field.geometry_term - ); + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); MFEM_VERIFY( state.Size() == Width(), "GravityFieldOperator received a " "preparation state with the wrong size." ); - const mfem::Vector density = make_read_only_value_view( - state, m_state_true_offsets, density_block - ); - const mfem::Vector displacement = make_read_only_value_view( - state, m_state_true_offsets, displacement_block - ); - const mfem::Vector gravity_gradient = make_read_only_value_view( - state, m_state_true_offsets, gravity_gradient_block - ); - const mfem::Vector gravity_potential = make_read_only_value_view( - state, m_state_true_offsets, gravity_potential_block - ); + const mfem::Vector density = make_read_only_value_view(state, m_state_true_offsets, density_block); + const mfem::Vector displacement = make_read_only_value_view(state, m_state_true_offsets, displacement_block); + const mfem::Vector gravity_gradient = + make_read_only_value_view(state, m_state_true_offsets, gravity_gradient_block); + const mfem::Vector gravity_potential = + make_read_only_value_view(state, m_state_true_offsets, gravity_potential_block); return m_linearization_context.Prepare( {.density = density, @@ -334,92 +254,65 @@ namespace mean_field::operators { ); } - const mfem::Array & - GravityFieldOperator::GetStateTrueOffsets() const noexcept { + const mfem::Array &GravityFieldOperator::GetStateTrueOffsets() const noexcept { return m_state_true_offsets; } - const mfem::Array & - GravityFieldOperator::GetResidualTrueOffsets() const noexcept { + const mfem::Array &GravityFieldOperator::GetResidualTrueOffsets() const noexcept { return m_residual_true_offsets; } void GravityFieldOperator::ApplyGravityUnknowns( const mfem::Vector &gravity_gradient, const mfem::Vector &gravity_potential, - const context::gravity_field::GravityFieldGeometryContext - &geometry_context, + const context::gravity_field::GravityFieldGeometryContext &geometry_context, mfem::Vector &action ) const { using form = utils::blocks::gravity_field_form; constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); - MFEM_VERIFY( - geometry_context.IsPrepared(), - "GravityFieldOperator received an unprepared geometry context." - ); + MFEM_VERIFY(geometry_context.IsPrepared(), "GravityFieldOperator received an unprepared geometry context."); MFEM_VERIFY( gravity_gradient.Size() == m_fem.gravityFluxFes->GetTrueVSize(), "GravityFieldOperator received a gravity-gradient vector with the " "wrong size." ); MFEM_VERIFY( - gravity_potential.Size() == - m_fem.gravityPotentialFes->GetTrueVSize(), + gravity_potential.Size() == m_fem.gravityPotentialFes->GetTrueVSize(), "GravityFieldOperator received a gravity-potential vector with the " "wrong size." ); action.SetSize(Height()); - action = 0.0; + action = 0.0; - mfem::Vector gravity_gradient_action = make_residual_view( - action, m_residual_true_offsets, gravity_gradient_residual_block - ); - mfem::Vector gravity_poisson_action = make_residual_view( - action, m_residual_true_offsets, gravity_poisson_residual_block - ); - mfem::Vector transpose_divergence_action( - gravity_gradient_action.Size() - ); + mfem::Vector gravity_gradient_action = + make_residual_view(action, m_residual_true_offsets, gravity_gradient_residual_block); + mfem::Vector gravity_poisson_action = + make_residual_view(action, m_residual_true_offsets, gravity_poisson_residual_block); + mfem::Vector transpose_divergence_action(gravity_gradient_action.Size()); - geometry_context.GetMassOperator().Mult( - gravity_gradient, gravity_gradient_action - ); - m_fem.gravityContext.BT->Mult( - gravity_potential, transpose_divergence_action - ); + geometry_context.GetMassOperator().Mult(gravity_gradient, gravity_gradient_action); + m_fem.gravityContext.BT->Mult(gravity_potential, transpose_divergence_action); gravity_gradient_action += transpose_divergence_action; - m_fem.gravityContext.b_form->Mult( - gravity_gradient, gravity_poisson_action - ); + m_fem.gravityContext.b_form->Mult(gravity_gradient, gravity_poisson_action); } void GravityFieldOperator::ApplyDensitySource( const mfem::Vector &density, - const context::gravity_field::GravityFieldGeometryContext - &geometry_context, + const context::gravity_field::GravityFieldGeometryContext &geometry_context, mfem::Vector &action ) const { using form = utils::blocks::gravity_field_form; constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); - MFEM_VERIFY( - geometry_context.IsPrepared(), - "GravityFieldOperator received an unprepared geometry context." - ); + MFEM_VERIFY(geometry_context.IsPrepared(), "GravityFieldOperator received an unprepared geometry context."); MFEM_VERIFY( density.Size() == m_fem.densityFes->GetTrueVSize(), "GravityFieldOperator received a density vector with the wrong " @@ -427,14 +320,11 @@ namespace mean_field::operators { ); action.SetSize(Height()); - action = 0.0; + action = 0.0; - mfem::Vector gravity_poisson_action = make_residual_view( - action, m_residual_true_offsets, gravity_poisson_residual_block - ); - geometry_context.GetSourceOperator().Mult( - density, gravity_poisson_action - ); + mfem::Vector gravity_poisson_action = + make_residual_view(action, m_residual_true_offsets, gravity_poisson_residual_block); + geometry_context.GetSourceOperator().Mult(density, gravity_poisson_action); } void GravityFieldOperator::Mult( @@ -445,51 +335,34 @@ namespace mean_field::operators { using form = utils::blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block( - utils::blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); + MFEM_VERIFY(state.Size() == Width(), "GravityFieldOperator received a state with the wrong size."); MFEM_VERIFY( - state.Size() == Width(), - "GravityFieldOperator received a state with the wrong size." - ); - MFEM_VERIFY( - m_linearization_context.IsPrepared(), - "GravityFieldOperator must be prepared before Mult is called." + m_linearization_context.IsPrepared(), "GravityFieldOperator must be prepared before Mult is called." ); - const mfem::Vector density = make_read_only_value_view( - state, m_state_true_offsets, density_block - ); - const mfem::Vector gravity_gradient = make_read_only_value_view( - state, m_state_true_offsets, gravity_gradient_block - ); - const mfem::Vector gravity_potential = make_read_only_value_view( - state, m_state_true_offsets, gravity_potential_block - ); - const context::gravity_field::GravityFieldGeometryContext - &geometry_context = m_linearization_context.GetGeometryContext(); + const mfem::Vector density = make_read_only_value_view(state, m_state_true_offsets, density_block); + const mfem::Vector gravity_gradient = + make_read_only_value_view(state, m_state_true_offsets, gravity_gradient_block); + const mfem::Vector gravity_potential = + make_read_only_value_view(state, m_state_true_offsets, gravity_potential_block); + const context::gravity_field::GravityFieldGeometryContext &geometry_context = + m_linearization_context.GetGeometryContext(); mfem::Vector source; - ApplyGravityUnknowns( - gravity_gradient, gravity_potential, geometry_context, residual - ); + ApplyGravityUnknowns(gravity_gradient, gravity_potential, geometry_context, residual); ApplyDensitySource(density, geometry_context, source); residual -= source; } - context::gravity_field::GravityFieldLinearizationContext & - GravityFieldOperator::GetLinearizationContext() noexcept { + context::gravity_field::GravityFieldLinearizationContext &GravityFieldOperator::GetLinearizationContext() noexcept { return m_linearization_context; } @@ -498,24 +371,21 @@ namespace mean_field::operators { return m_linearization_context; } - mfem::Operator & - GravityFieldOperator::GetGradient(const mfem::Vector &state) const { + mfem::Operator &GravityFieldOperator::GetGradient(const mfem::Vector &state) const { MFEM_VERIFY( state.Size() == Width(), "GravityFieldOperator received a " "linearization state with the wrong size." ); MFEM_VERIFY( - m_linearization_context.IsPrepared(), - "GravityFieldOperator must be prepared before GetGradient is " - "called." + m_linearization_context.IsPrepared(), "GravityFieldOperator must be prepared before GetGradient is " + "called." ); return m_jacobian; } ReducedGravityFieldOperator::ReducedGravityFieldOperator( GravityFieldOperator &gravity_field_operator, - context::gravity_field::GravityFieldGeometryContext - &gravity_field_geometry_context, + context::gravity_field::GravityFieldGeometryContext &gravity_field_geometry_context, const mfem::Vector &displacement ) : Operator( @@ -523,31 +393,20 @@ namespace mean_field::operators { gravity_field_operator.Height() ), m_gravity_field_operator(gravity_field_operator), - m_gravity_true_offsets( - gravity_field_operator.GetResidualTrueOffsets() - ), + m_gravity_true_offsets(gravity_field_operator.GetResidualTrueOffsets()), m_gravity_field_geometry_context(gravity_field_geometry_context) { using form = utils::blocks::gravity_field_form; constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); - const mfem::Array &state_offsets = - m_gravity_field_operator.GetStateTrueOffsets(); + const mfem::Array &state_offsets = m_gravity_field_operator.GetStateTrueOffsets(); MFEM_VERIFY( state_offsets.Size() == form::value_block_count + 1, @@ -558,13 +417,8 @@ namespace mean_field::operators { "ReducedGravityFieldOperator received an invalid gravity-residual " "layout." ); - MFEM_VERIFY( - state_offsets[0] == 0, "The full-state offsets must begin at zero." - ); - MFEM_VERIFY( - m_gravity_true_offsets[0] == 0, - "The reduced gravity offsets must begin at zero." - ); + MFEM_VERIFY(state_offsets[0] == 0, "The full-state offsets must begin at zero."); + MFEM_VERIFY(m_gravity_true_offsets[0] == 0, "The reduced gravity offsets must begin at zero."); MFEM_VERIFY( state_offsets.Last() == m_gravity_field_operator.Width(), "The full-state offsets do not match the gravity-field operator " @@ -576,23 +430,17 @@ namespace mean_field::operators { "operator " "height." ); - MFEM_VERIFY( - Width() == Height(), "ReducedGravityFieldOperator must be square." - ); + MFEM_VERIFY(Width() == Height(), "ReducedGravityFieldOperator must be square."); const int full_gradient_size = - state_offsets[static_cast(gravity_gradient_block) + 1] - - state_offsets[gravity_gradient_block]; + state_offsets[static_cast(gravity_gradient_block) + 1] - state_offsets[gravity_gradient_block]; const int full_potential_size = - state_offsets[static_cast(gravity_potential_block) + 1] - - state_offsets[gravity_potential_block]; + state_offsets[static_cast(gravity_potential_block) + 1] - state_offsets[gravity_potential_block]; const int reduced_gradient_size = - m_gravity_true_offsets - [static_cast(gravity_gradient_residual_block) + 1] - + m_gravity_true_offsets[static_cast(gravity_gradient_residual_block) + 1] - m_gravity_true_offsets[gravity_gradient_residual_block]; const int reduced_potential_size = - m_gravity_true_offsets - [static_cast(gravity_poisson_residual_block) + 1] - + m_gravity_true_offsets[static_cast(gravity_poisson_residual_block) + 1] - m_gravity_true_offsets[gravity_poisson_residual_block]; MFEM_VERIFY( @@ -609,31 +457,24 @@ namespace mean_field::operators { SetDisplacement(displacement); } - void ReducedGravityFieldOperator::SetDisplacement( - const mfem::Vector &displacement - ) { + void ReducedGravityFieldOperator::SetDisplacement(const mfem::Vector &displacement) { ValidateDisplacement(displacement); context::gravity_field::DiscretizationRevision discretization_revision; context::gravity_field::DisplacementRevision displacement_revision; if (m_gravity_field_geometry_context.IsPrepared()) { - discretization_revision = - m_gravity_field_geometry_context.GetDiscretizationRevision(); - displacement_revision = - m_gravity_field_geometry_context.GetDisplacementRevision(); + discretization_revision = m_gravity_field_geometry_context.GetDiscretizationRevision(); + displacement_revision = m_gravity_field_geometry_context.GetDisplacementRevision(); MFEM_VERIFY( - displacement_revision.value < - std::numeric_limits::max(), + displacement_revision.value < std::numeric_limits::max(), "The reduced gravity displacement revision has overflowed." ); ++displacement_revision.value; } - m_gravity_field_geometry_context.Prepare( - displacement, discretization_revision, displacement_revision - ); + m_gravity_field_geometry_context.Prepare(displacement, discretization_revision, displacement_revision); } const mfem::Vector &ReducedGravityFieldOperator::GetDisplacement() const { @@ -646,15 +487,12 @@ namespace mean_field::operators { ) const { ValidateDensity(density); - m_gravity_field_operator.ApplyDensitySource( - density, m_gravity_field_geometry_context, right_hand_side - ); + m_gravity_field_operator.ApplyDensitySource(density, m_gravity_field_geometry_context, right_hand_side); MFEM_VERIFY( - right_hand_side.Size() == Height(), - "ReducedGravityFieldOperator produced a right-hand side with the " - "wrong " - "size." + right_hand_side.Size() == Height(), "ReducedGravityFieldOperator produced a right-hand side with the " + "wrong " + "size." ); } @@ -667,29 +505,19 @@ namespace mean_field::operators { using form = utils::blocks::gravity_field_form; constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); ValidateGravityState(gravity_state); - const mfem::Vector gravity_gradient_true = make_read_only_residual_view( - gravity_state, m_gravity_true_offsets, - gravity_gradient_residual_block - ); + const mfem::Vector gravity_gradient_true = + make_read_only_residual_view(gravity_state, m_gravity_true_offsets, gravity_gradient_residual_block); const mfem::Vector gravity_potential_true = - make_read_only_residual_view( - gravity_state, m_gravity_true_offsets, - gravity_poisson_residual_block - ); + make_read_only_residual_view(gravity_state, m_gravity_true_offsets, gravity_poisson_residual_block); m_gravity_field_operator.ApplyGravityUnknowns( - gravity_gradient_true, gravity_potential_true, - m_gravity_field_geometry_context, action + gravity_gradient_true, gravity_potential_true, m_gravity_field_geometry_context, action ); MFEM_VERIFY( @@ -698,18 +526,15 @@ namespace mean_field::operators { ); } - GravityFieldOperator & - ReducedGravityFieldOperator::GetGravityFieldOperator() noexcept { + GravityFieldOperator &ReducedGravityFieldOperator::GetGravityFieldOperator() noexcept { return m_gravity_field_operator; } - const GravityFieldOperator & - ReducedGravityFieldOperator::GetGravityFieldOperator() const noexcept { + const GravityFieldOperator &ReducedGravityFieldOperator::GetGravityFieldOperator() const noexcept { return m_gravity_field_operator; } - context::gravity_field::GravityFieldGeometryContext & - ReducedGravityFieldOperator::GetGeometryContext() noexcept { + context::gravity_field::GravityFieldGeometryContext &ReducedGravityFieldOperator::GetGeometryContext() noexcept { return m_gravity_field_geometry_context; } @@ -718,73 +543,53 @@ namespace mean_field::operators { return m_gravity_field_geometry_context; } - const mfem::Array & - ReducedGravityFieldOperator::GetGravityTrueOffsets() const noexcept { + const mfem::Array &ReducedGravityFieldOperator::GetGravityTrueOffsets() const noexcept { return m_gravity_true_offsets; } - void ReducedGravityFieldOperator::ValidateDisplacement( - const mfem::Vector &displacement - ) const { + void ReducedGravityFieldOperator::ValidateDisplacement(const mfem::Vector &displacement) const { using form = utils::blocks::gravity_field_form; constexpr auto displacement_block = - utils::blocks::get_value_block( - utils::blocks::displacement_field.geometry_term - ); + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); - const mfem::Array &state_offsets = - m_gravity_field_operator.GetStateTrueOffsets(); + const mfem::Array &state_offsets = m_gravity_field_operator.GetStateTrueOffsets(); const int expected_size = - state_offsets[static_cast(displacement_block) + 1] - - state_offsets[displacement_block]; + state_offsets[static_cast(displacement_block) + 1] - state_offsets[displacement_block]; MFEM_VERIFY( - displacement.Size() == expected_size, - "ReducedGravityFieldOperator received a displacement with the " - "wrong " - "size." + displacement.Size() == expected_size, "ReducedGravityFieldOperator received a displacement with the " + "wrong " + "size." ); for (int i = 0; i < displacement.Size(); ++i) { MFEM_VERIFY( - std::isfinite(displacement(i)), - "ReducedGravityFieldOperator received a non-finite " - "displacement " - "value." + std::isfinite(displacement(i)), "ReducedGravityFieldOperator received a non-finite " + "displacement " + "value." ); } } - void ReducedGravityFieldOperator::ValidateDensity( - const mfem::Vector &density - ) const { + void ReducedGravityFieldOperator::ValidateDensity(const mfem::Vector &density) const { using form = utils::blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block( - utils::blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); - const mfem::Array &state_offsets = - m_gravity_field_operator.GetStateTrueOffsets(); - const int expected_size = - state_offsets[static_cast(density_block) + 1] - - state_offsets[density_block]; + const mfem::Array &state_offsets = m_gravity_field_operator.GetStateTrueOffsets(); + const int expected_size = state_offsets[static_cast(density_block) + 1] - state_offsets[density_block]; MFEM_VERIFY( - density.Size() == expected_size, - "ReducedGravityFieldOperator received a density with the wrong " - "size." + density.Size() == expected_size, "ReducedGravityFieldOperator received a density with the wrong " + "size." ); } - void ReducedGravityFieldOperator::ValidateGravityState( - const mfem::Vector &gravity_state - ) const { + void ReducedGravityFieldOperator::ValidateGravityState(const mfem::Vector &gravity_state) const { MFEM_VERIFY( - gravity_state.Size() == Width(), - "ReducedGravityFieldOperator received " - "a gravity state with the wrong size." + gravity_state.Size() == Width(), "ReducedGravityFieldOperator received " + "a gravity state with the wrong size." ); } } // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/gravity_field_jacobian.cpp b/libmeanfield/impl/operators/gravity_field_jacobian.cpp index 00bae60..9242324 100644 --- a/libmeanfield/impl/operators/gravity_field_jacobian.cpp +++ b/libmeanfield/impl/operators/gravity_field_jacobian.cpp @@ -15,9 +15,7 @@ namespace { ) { const int offset = offsets[index]; const int size = offsets[index + 1] - offset; - return mfem::Vector( - const_cast(vector.GetData()) + offset, size - ); + return mfem::Vector(const_cast(vector.GetData()) + offset, size); } template @@ -56,10 +54,7 @@ namespace { MFEM_VERIFY(offsets[0] == 0, "Block offsets must begin at zero."); for (int i = 0; i < block_count; ++i) - MFEM_VERIFY( - offsets[i + 1] >= offsets[i], - "Block offsets must be nondecreasing." - ); + MFEM_VERIFY(offsets[i + 1] >= offsets[i], "Block offsets must be nondecreasing."); } void validate_layout( @@ -70,29 +65,18 @@ namespace { using form = mean_field::utils::blocks::gravity_field_form; constexpr auto density_block = - mean_field::utils::blocks::get_value_block( - mean_field::utils::blocks::density_field.mass_term - ); - constexpr auto displacement_block = - mean_field::utils::blocks::get_value_block( - mean_field::utils::blocks::displacement_field.geometry_term - ); + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + constexpr auto displacement_block = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); constexpr auto gravity_gradient_block = - mean_field::utils::blocks::get_value_block( - mean_field::utils::blocks::gravity_field.gradient_term - ); + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - mean_field::utils::blocks::get_value_block( - mean_field::utils::blocks::gravity_field.poisson_term - ); + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); constexpr auto gravity_gradient_residual_block = - mean_field::utils::blocks::get_residual_block( - mean_field::utils::blocks::gravity_field.gradient_term - ); + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - mean_field::utils::blocks::get_residual_block( - mean_field::utils::blocks::gravity_field.poisson_term - ); + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::gravity_field.poisson_term); validate_offsets( state_offsets, form::value_block_count, @@ -106,33 +90,27 @@ namespace { ); MFEM_VERIFY( - get_block_size(state_offsets, density_block) == - f.densityFes->GetTrueVSize(), + get_block_size(state_offsets, density_block) == f.densityFes->GetTrueVSize(), "The Jacobian density block has the wrong size." ); MFEM_VERIFY( - get_block_size(state_offsets, displacement_block) == - f.displacementFes->GetTrueVSize(), + get_block_size(state_offsets, displacement_block) == f.displacementFes->GetTrueVSize(), "The Jacobian displacement block has the wrong size." ); MFEM_VERIFY( - get_block_size(state_offsets, gravity_gradient_block) == - f.gravityFluxFes->GetTrueVSize(), + get_block_size(state_offsets, gravity_gradient_block) == f.gravityFluxFes->GetTrueVSize(), "The Jacobian gravity-gradient block has the wrong size." ); MFEM_VERIFY( - get_block_size(state_offsets, gravity_potential_block) == - f.gravityPotentialFes->GetTrueVSize(), + get_block_size(state_offsets, gravity_potential_block) == f.gravityPotentialFes->GetTrueVSize(), "The Jacobian gravity-potential block has the wrong size." ); MFEM_VERIFY( - get_block_size(residual_offsets, gravity_gradient_residual_block) == - f.gravityFluxFes->GetTrueVSize(), + get_block_size(residual_offsets, gravity_gradient_residual_block) == f.gravityFluxFes->GetTrueVSize(), "The Jacobian gradient-residual block has the wrong size." ); MFEM_VERIFY( - get_block_size(residual_offsets, gravity_poisson_residual_block) == - f.gravityPotentialFes->GetTrueVSize(), + get_block_size(residual_offsets, gravity_poisson_residual_block) == f.gravityPotentialFes->GetTrueVSize(), "The Jacobian Poisson-residual block has the wrong size." ); } @@ -142,8 +120,7 @@ namespace mean_field::operators { GravityFieldJacobianOperator::GravityFieldJacobianOperator( fem::FEM &f, const mapping::DomainMapperStateless &domain_mapper, - const context::gravity_field::GravityFieldLinearizationContext - &linearization_context, + const context::gravity_field::GravityFieldLinearizationContext &linearization_context, const mfem::Array &state_true_offsets, const mfem::Array &residual_true_offsets ) @@ -157,37 +134,30 @@ namespace mean_field::operators { m_state_true_offsets(state_true_offsets), m_residual_true_offsets(residual_true_offsets) { MFEM_VERIFY( - f.densityFes != nullptr, - "GravityFieldJacobianOperator requires the density finite-element " - "space." + f.densityFes != nullptr, "GravityFieldJacobianOperator requires the density finite-element " + "space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "GravityFieldJacobianOperator requires the gravity-potential " - "finite-element space." + f.gravityPotentialFes != nullptr, "GravityFieldJacobianOperator requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "GravityFieldJacobianOperator requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "GravityFieldJacobianOperator requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "GravityFieldJacobianOperator requires the " - "displacement finite-element space." + f.displacementFes != nullptr, "GravityFieldJacobianOperator requires the " + "displacement finite-element space." ); MFEM_VERIFY( - f.gravityContext.b_form != nullptr, - "GravityFieldJacobianOperator requires the divergence operator." + f.gravityContext.b_form != nullptr, "GravityFieldJacobianOperator requires the divergence operator." ); MFEM_VERIFY( - f.gravityContext.BT != nullptr, - "GravityFieldJacobianOperator requires the transpose divergence " - "operator." + f.gravityContext.BT != nullptr, "GravityFieldJacobianOperator requires the transpose divergence " + "operator." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "GravityFieldJacobianOperator requires the quadrature-rule factory." + f.quadratureFactory != nullptr, "GravityFieldJacobianOperator requires the quadrature-rule factory." ); MFEM_VERIFY( domain_mapper.GetDimension() == f.mesh->Dimension(), @@ -204,107 +174,74 @@ namespace mean_field::operators { mfem::Vector &action ) const { MFEM_VERIFY( - m_linearization_context.IsPrepared(), - "GravityFieldJacobianOperator requires a prepared linearization " - "context." + m_linearization_context.IsPrepared(), "GravityFieldJacobianOperator requires a prepared linearization " + "context." ); MFEM_VERIFY( - direction.Size() == Width(), - "GravityFieldJacobianOperator received a direction with the wrong " - "size." + direction.Size() == Width(), "GravityFieldJacobianOperator received a direction with the wrong " + "size." ); using form = utils::blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block( - utils::blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); constexpr auto displacement_block = - utils::blocks::get_value_block( - utils::blocks::displacement_field.geometry_term - ); + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_potential_block = - utils::blocks::get_value_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); - const context::gravity_field::GravityFieldGeometryContext - &geometry_context = m_linearization_context.GetGeometryContext(); - const mfem::Vector &density = m_linearization_context.GetDensity(); - const mfem::Vector &displacement = geometry_context.GetDisplacement(); - const mfem::Vector &gravity_gradient = - m_linearization_context.GetGravityGradient(); + const context::gravity_field::GravityFieldGeometryContext &geometry_context = + m_linearization_context.GetGeometryContext(); + const mfem::Vector &density = m_linearization_context.GetDensity(); + const mfem::Vector &displacement = geometry_context.GetDisplacement(); + const mfem::Vector &gravity_gradient = m_linearization_context.GetGravityGradient(); - const mfem::Vector density_direction = make_read_only_value_view( - direction, m_state_true_offsets, density_block - ); - const mfem::Vector displacement_direction = make_read_only_value_view( - direction, m_state_true_offsets, displacement_block - ); + const mfem::Vector density_direction = + make_read_only_value_view(direction, m_state_true_offsets, density_block); + const mfem::Vector displacement_direction = + make_read_only_value_view(direction, m_state_true_offsets, displacement_block); const mfem::Vector gravity_gradient_direction = - make_read_only_value_view( - direction, m_state_true_offsets, gravity_gradient_block - ); + make_read_only_value_view(direction, m_state_true_offsets, gravity_gradient_block); const mfem::Vector gravity_potential_direction = - make_read_only_value_view( - direction, m_state_true_offsets, gravity_potential_block - ); + make_read_only_value_view(direction, m_state_true_offsets, gravity_potential_block); action.SetSize(Height()); - action = 0.0; + action = 0.0; - mfem::Vector gravity_gradient_action = make_residual_view( - action, m_residual_true_offsets, gravity_gradient_residual_block - ); - mfem::Vector gravity_poisson_action = make_residual_view( - action, m_residual_true_offsets, gravity_poisson_residual_block - ); + mfem::Vector gravity_gradient_action = + make_residual_view(action, m_residual_true_offsets, gravity_gradient_residual_block); + mfem::Vector gravity_poisson_action = + make_residual_view(action, m_residual_true_offsets, gravity_poisson_residual_block); mfem::Vector transpose_divergence_action; mfem::Vector source_action; mfem::Vector mass_variation_action; mfem::Vector source_variation_action; - geometry_context.GetMassOperator().Mult( - gravity_gradient_direction, gravity_gradient_action - ); - geometry_context.GetSourceOperator().Mult( - density_direction, source_action - ); + geometry_context.GetMassOperator().Mult(gravity_gradient_direction, gravity_gradient_action); + geometry_context.GetSourceOperator().Mult(density_direction, source_action); kernels::apply_mapped_hdiv_mass_variation( - m_fem, m_domain_mapper, gravity_gradient, displacement, - displacement_direction, mass_variation_action + m_fem, m_domain_mapper, gravity_gradient, displacement, displacement_direction, mass_variation_action ); kernels::apply_mapped_source_variation( - m_fem, m_domain_mapper, density, displacement, - displacement_direction, source_variation_action + m_fem, m_domain_mapper, density, displacement, displacement_direction, source_variation_action ); transpose_divergence_action.SetSize(gravity_gradient_action.Size()); - m_fem.gravityContext.BT->Mult( - gravity_potential_direction, transpose_divergence_action - ); + m_fem.gravityContext.BT->Mult(gravity_potential_direction, transpose_divergence_action); gravity_gradient_action += transpose_divergence_action; gravity_gradient_action += mass_variation_action; - m_fem.gravityContext.b_form->Mult( - gravity_gradient_direction, gravity_poisson_action - ); + m_fem.gravityContext.b_form->Mult(gravity_gradient_direction, gravity_poisson_action); gravity_poisson_action -= source_action; gravity_poisson_action -= source_variation_action; diff --git a/libmeanfield/impl/operators/kernels/barotropic_closure_kernels.cpp b/libmeanfield/impl/operators/kernels/barotropic_closure_kernels.cpp index 4390193..8ea2c21 100644 --- a/libmeanfield/impl/operators/kernels/barotropic_closure_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/barotropic_closure_kernels.cpp @@ -8,24 +8,29 @@ module; module mean_field; import :operators.kernels.barotropic_closure; +import :field.registry; +import :utils.domain; namespace { + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + using ClosureDomain = mean_field::field::FieldDomainT; + enum class ClosureAction { residual, density, enthalpy }; + [[nodiscard]] bool element_is_in_closure_support(const int attribute) { + return DomainSchema::template attribute_belongs_to(attribute); + } + void true_to_local( const mfem::ParFiniteElementSpace &finiteElementSpace, const mfem::Vector &trueVector, mfem::Vector &localVector ) { - MFEM_VERIFY( - trueVector.Size() == finiteElementSpace.GetTrueVSize(), - "True vector has the wrong size." - ); + MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size."); localVector.SetSize(finiteElementSpace.GetVSize()); - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(trueVector, localVector); @@ -39,16 +44,12 @@ namespace { const mfem::Vector &localVector, mfem::Vector &trueVector ) { - MFEM_VERIFY( - localVector.Size() == finiteElementSpace.GetVSize(), - "Local vector has the wrong size." - ); + MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "Local vector has the wrong size."); trueVector.SetSize(finiteElementSpace.GetTrueVSize()); - trueVector = 0.0; + trueVector = 0.0; - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(localVector, trueVector); @@ -57,19 +58,13 @@ namespace { } } - int get_eos_extra_order( - const mean_field::physics::PolytropicBarotrope &barotrope - ) { - const double extraOrder = - (barotrope.polytropic_index() - 1.0) * - static_cast( - mean_field::field::Enthalpy::Scalar::familyOrder - ); + int get_eos_extra_order(const mean_field::eos::Polytrope &barotrope) { + const double extraOrder = (barotrope.polytropic_index() - 1.0) * + static_cast(mean_field::field::Enthalpy::Scalar::familyOrder); MFEM_VERIFY( std::isfinite(extraOrder) && extraOrder >= 0.0 && - extraOrder <= - static_cast(std::numeric_limits::max()), + extraOrder <= static_cast(std::numeric_limits::max()), "The EOS effective polynomial order is invalid." ); @@ -78,44 +73,37 @@ namespace { const mfem::IntegrationRule &get_eos_rule( const mean_field::fem::FEM &f, - const mean_field::physics::PolytropicBarotrope &barotrope, + const mean_field::eos::Polytrope &barotrope, const mfem::FiniteElement &densityElement, const mfem::FiniteElement &enthalpyElement, const mfem::ElementTransformation &transformation ) { - using EnthalpyField = - mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; MFEM_VERIFY( - densityElement.GetOrder() == - mean_field::field::Density::Scalar::familyOrder, + densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder, "The EOS test element does not match the " "registered density field." ); MFEM_VERIFY( - enthalpyElement.GetOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder, + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, "The EOS trial element does not match the " "registered enthalpy field." ); - const mean_field::quadrature::Query query = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EosClosureSource>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), - std::array{get_eos_extra_order(barotrope)}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general - ); + const mean_field::quadrature::Query query = + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), + std::array{get_eos_extra_order(barotrope)}, mean_field::utils::DOMAINS::STELLAR, + mean_field::quadrature::MappingKind::general + ); - const auto resolution = - f.quadratureFactory->get(query, transformation.GetGeometryType()); + const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( - resolution.integration_rule != nullptr, - "The quadrature policy did not return an " - "EOS-closure integration rule." + resolution.integration_rule != nullptr, "The quadrature policy did not return an " + "EOS-closure integration rule." ); return *resolution.integration_rule; @@ -126,54 +114,44 @@ namespace { const mean_field::mapping::DomainMapperStateless &domainMapper, const mfem::Vector &displacementTrue ) { + MFEM_VERIFY(f.mesh != nullptr, "The EOS closure kernel requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, "The EOS closure kernel requires a mesh." + f.densityFes != nullptr, "The EOS closure kernel requires the density " + "finite-element space." ); MFEM_VERIFY( - f.densityFes != nullptr, - "The EOS closure kernel requires the density " - "finite-element space." + f.enthalpyFes != nullptr, "The EOS closure kernel requires the enthalpy " + "finite-element space." ); MFEM_VERIFY( - f.enthalpyFes != nullptr, - "The EOS closure kernel requires the enthalpy " - "finite-element space." + f.displacementFes != nullptr, "The EOS closure kernel requires the displacement " + "finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The EOS closure kernel requires the displacement " - "finite-element space." + f.compactificationFes != nullptr, "The EOS closure kernel requires the " + "compactification finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The EOS closure kernel requires the " - "compactification finite-element space." + f.compactificationCoordinate != nullptr, "The EOS closure kernel requires the " + "compactification coordinate." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The EOS closure kernel requires the " - "compactification coordinate." + f.quadratureFactory != nullptr, "The EOS closure kernel requires the quadrature " + "rule factory." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The EOS closure kernel requires the quadrature " - "rule factory." + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); MFEM_VERIFY( - displacementTrue.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." - ); - MFEM_VERIFY( - domainMapper.GetDimension() == f.mesh->Dimension(), - "The domain-mapper dimension does not match " - "the mesh dimension." + domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match " + "the mesh dimension." ); } void apply_closure_action( const mean_field::fem::FEM &f, const mean_field::mapping::DomainMapperStateless &domainMapper, - const mean_field::physics::PolytropicBarotrope &barotrope, + const mean_field::eos::Polytrope &barotrope, const ClosureAction closureAction, const mfem::Vector *densityInputTrue, const mfem::Vector *baseEnthalpyTrue, @@ -183,29 +161,23 @@ namespace { ) { validate_common_inputs(f, domainMapper, displacementTrue); - if (closureAction == ClosureAction::residual || - closureAction == ClosureAction::density) { + if (closureAction == ClosureAction::residual || closureAction == ClosureAction::density) { MFEM_VERIFY( - densityInputTrue != nullptr && - densityInputTrue->Size() == f.densityFes->GetTrueVSize(), + densityInputTrue != nullptr && densityInputTrue->Size() == f.densityFes->GetTrueVSize(), "The density input has the wrong size." ); } - if (closureAction == ClosureAction::residual || - closureAction == ClosureAction::enthalpy) { + if (closureAction == ClosureAction::residual || closureAction == ClosureAction::enthalpy) { MFEM_VERIFY( - baseEnthalpyTrue != nullptr && - baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(), + baseEnthalpyTrue != nullptr && baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(), "The base enthalpy has the wrong size." ); } if (closureAction == ClosureAction::enthalpy) { MFEM_VERIFY( - enthalpyVariationTrue != nullptr && - enthalpyVariationTrue->Size() == - f.enthalpyFes->GetTrueVSize(), + enthalpyVariationTrue != nullptr && enthalpyVariationTrue->Size() == f.enthalpyFes->GetTrueVSize(), "The enthalpy variation has the wrong size." ); } @@ -224,9 +196,7 @@ namespace { } if (enthalpyVariationTrue != nullptr) { - true_to_local( - *f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal - ); + true_to_local(*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal); } true_to_local(*f.displacementFes, displacementTrue, displacementLocal); @@ -234,9 +204,7 @@ namespace { mfem::Vector localAction(f.densityFes->GetVSize()); localAction = 0.0; - mean_field::mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array densityDofs; mfem::Array enthalpyDofs; @@ -253,150 +221,105 @@ namespace { mfem::Vector densityShape; mfem::Vector enthalpyShape; - const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); - for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "The EOS closure kernel received a null " - "element transformation." + transformation != nullptr, "The EOS closure kernel received a null " + "element transformation." ); - if (transformation->Attribute == vacuumAttribute) { + if (!element_is_in_closure_support(transformation->Attribute)) { continue; } - const mfem::FiniteElement &densityElement = - *f.densityFes->GetFE(elementId); - const mfem::FiniteElement &enthalpyElement = - *f.enthalpyFes->GetFE(elementId); - const mfem::FiniteElement &displacementElement = - *f.displacementFes->GetFE(elementId); - const mfem::FiniteElement &compactificationElement = - *f.compactificationFes->GetFE(elementId); + const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); - mfem::DofTransformation *densityDofTransformation = - f.densityFes->GetElementDofs(elementId, densityDofs); + mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs); - mfem::DofTransformation *enthalpyDofTransformation = - f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); + mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); mfem::DofTransformation *displacementDofTransformation = f.displacementFes->GetElementVDofs(elementId, displacementDofs); mfem::DofTransformation *compactificationDofTransformation = - f.compactificationFes->GetElementDofs( - elementId, compactificationDofs - ); + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); if (densityInputTrue != nullptr) { - densityInputLocal.GetSubVector( - densityDofs, elementDensityInput - ); + densityInputLocal.GetSubVector(densityDofs, elementDensityInput); if (densityDofTransformation != nullptr) { - densityDofTransformation->InvTransformPrimal( - elementDensityInput - ); + densityDofTransformation->InvTransformPrimal(elementDensityInput); } } if (baseEnthalpyTrue != nullptr) { - baseEnthalpyLocal.GetSubVector( - enthalpyDofs, elementBaseEnthalpy - ); + baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy); if (enthalpyDofTransformation != nullptr) { - enthalpyDofTransformation->InvTransformPrimal( - elementBaseEnthalpy - ); + enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy); } } if (enthalpyVariationTrue != nullptr) { - enthalpyVariationLocal.GetSubVector( - enthalpyDofs, elementEnthalpyVariation - ); + enthalpyVariationLocal.GetSubVector(enthalpyDofs, elementEnthalpyVariation); if (enthalpyDofTransformation != nullptr) { - enthalpyDofTransformation->InvTransformPrimal( - elementEnthalpyVariation - ); + enthalpyDofTransformation->InvTransformPrimal(elementEnthalpyVariation); } } - displacementLocal.GetSubVector( - displacementDofs, elementDisplacement - ); + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); - f.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (displacementDofTransformation != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacement); } if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } - const mean_field::mapping::ElementDisplacementData - displacementData = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ); + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); - const mean_field::mapping::ElementCompactificationData - compactificationData( - compactificationElement, elementCompactification - ); + const mean_field::mapping::ElementCompactificationData compactificationData( + compactificationElement, elementCompactification + ); const mean_field::mapping::ElementMappingData mappingData{ - .displacement = displacementData, - .compactification = compactificationData + .displacement = displacementData, .compactification = compactificationData }; densityShape.SetSize(densityElement.GetDof()); enthalpyShape.SetSize(enthalpyElement.GetDof()); elementAction.SetSize(densityElement.GetDof()); - elementAction = 0.0; + elementAction = 0.0; - const mfem::IntegrationRule &integrationRule = get_eos_rule( - f, barotrope, densityElement, enthalpyElement, *transformation - ); + const mfem::IntegrationRule &integrationRule = + get_eos_rule(f, barotrope, densityElement, enthalpyElement, *transformation); - for (int quadratureIndex = 0; - quadratureIndex < integrationRule.GetNPoints(); - ++quadratureIndex) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(quadratureIndex); + for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex); transformation->SetIntPoint(&integrationPoint); mean_field::mapping::VolumeMappingContext mappingContext; - const mean_field::mapping::MappingStatus mappingStatus = - domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( mappingStatus == mean_field::mapping::MappingStatus::valid, "Stateless mapping failed in the EOS " "closure kernel. Element: " - << elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadratureIndex - << ", status: " << static_cast(mappingStatus) + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadratureIndex << ", status: " << static_cast(mappingStatus) ); densityElement.CalcShape(integrationPoint, densityShape); @@ -408,34 +331,23 @@ namespace { } else { enthalpyElement.CalcShape(integrationPoint, enthalpyShape); - const double baseEnthalpy = - elementBaseEnthalpy * enthalpyShape; + const double baseEnthalpy = elementBaseEnthalpy * enthalpyShape; if (closureAction == ClosureAction::residual) { - const double density = - elementDensityInput * densityShape; + const double density = elementDensityInput * densityShape; - integrand = - density - - barotrope.density_from_enthalpy(baseEnthalpy); + integrand = density - barotrope.density_from_enthalpy(baseEnthalpy); } else { - const double enthalpyVariation = - elementEnthalpyVariation * enthalpyShape; + const double enthalpyVariation = elementEnthalpyVariation * enthalpyShape; - integrand = -barotrope.density_derivative_from_enthalpy( - baseEnthalpy - ) * - enthalpyVariation; + integrand = -barotrope.density_derivative_from_enthalpy(baseEnthalpy) * enthalpyVariation; } } - const double weightedIntegrand = - mappingContext.quadrature.weight * integrand; + const double weightedIntegrand = mappingContext.quadrature.weight * integrand; - for (int densityDof = 0; densityDof < densityElement.GetDof(); - ++densityDof) { - elementAction(densityDof) += - weightedIntegrand * densityShape(densityDof); + for (int densityDof = 0; densityDof < densityElement.GetDof(); ++densityDof) { + elementAction(densityDof) += weightedIntegrand * densityShape(densityDof); } } @@ -454,51 +366,51 @@ namespace mean_field::operators::kernels { void apply_barotropic_closure( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &barotrope, const mfem::Vector &densityTrue, const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue, mfem::Vector &residual ) { apply_closure_action( - f, domainMapper, barotrope, ClosureAction::residual, &densityTrue, - &enthalpyTrue, nullptr, displacementTrue, residual + f, domainMapper, barotrope, ClosureAction::residual, &densityTrue, &enthalpyTrue, nullptr, displacementTrue, + residual ); } void apply_barotropic_closure_density_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &barotrope, const mfem::Vector &densityVariationTrue, const mfem::Vector &displacementTrue, mfem::Vector &action ) { apply_closure_action( - f, domainMapper, barotrope, ClosureAction::density, - &densityVariationTrue, nullptr, nullptr, displacementTrue, action + f, domainMapper, barotrope, ClosureAction::density, &densityVariationTrue, nullptr, nullptr, + displacementTrue, action ); } void apply_barotropic_closure_enthalpy_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &barotrope, const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &displacementTrue, mfem::Vector &action ) { apply_closure_action( - f, domainMapper, barotrope, ClosureAction::enthalpy, nullptr, - &baseEnthalpyTrue, &enthalpyVariationTrue, displacementTrue, action + f, domainMapper, barotrope, ClosureAction::enthalpy, nullptr, &baseEnthalpyTrue, &enthalpyVariationTrue, + displacementTrue, action ); } void apply_barotropic_closure_displacement_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &barotrope, const mfem::Vector &baseDensityTrue, const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &displacementTrue, @@ -511,66 +423,55 @@ namespace mean_field::operators::kernels { ); MFEM_VERIFY( - f.densityFes != nullptr, - "The barotropic-closure displacement action " - "requires the density finite-element space." + f.densityFes != nullptr, "The barotropic-closure displacement action " + "requires the density finite-element space." ); MFEM_VERIFY( - f.enthalpyFes != nullptr, - "The barotropic-closure displacement action " - "requires the enthalpy finite-element space." + f.enthalpyFes != nullptr, "The barotropic-closure displacement action " + "requires the enthalpy finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The barotropic-closure displacement action " - "requires the displacement finite-element space." + f.displacementFes != nullptr, "The barotropic-closure displacement action " + "requires the displacement finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The barotropic-closure displacement action " - "requires the compactification finite-element space." + f.compactificationFes != nullptr, "The barotropic-closure displacement action " + "requires the compactification finite-element space." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The barotropic-closure displacement action " - "requires the compactification coordinate." + f.compactificationCoordinate != nullptr, "The barotropic-closure displacement action " + "requires the compactification coordinate." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The barotropic-closure displacement action " - "requires the quadrature-rule factory." + f.quadratureFactory != nullptr, "The barotropic-closure displacement action " + "requires the quadrature-rule factory." ); MFEM_VERIFY( - baseDensityTrue.Size() == f.densityFes->GetTrueVSize(), - "The base-density vector has the wrong size." + baseDensityTrue.Size() == f.densityFes->GetTrueVSize(), "The base-density vector has the wrong size." ); MFEM_VERIFY( - baseEnthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(), - "The base-enthalpy vector has the wrong size." + baseEnthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(), "The base-enthalpy vector has the wrong size." ); MFEM_VERIFY( - displacementTrue.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); MFEM_VERIFY( - displacementVariationTrue.Size() == - f.displacementFes->GetTrueVSize(), + displacementVariationTrue.Size() == f.displacementFes->GetTrueVSize(), "The displacement-variation vector has the wrong size." ); MFEM_VERIFY( - domainMapper.GetDimension() == f.mesh->Dimension(), - "The domain-mapper dimension does not match the " - "mesh dimension." + domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match the " + "mesh dimension." ); mfem::Vector baseDensityLocal; @@ -584,17 +485,12 @@ namespace mean_field::operators::kernels { true_to_local(*f.displacementFes, displacementTrue, displacementLocal); - true_to_local( - *f.displacementFes, displacementVariationTrue, - displacementVariationLocal - ); + true_to_local(*f.displacementFes, displacementVariationTrue, displacementVariationLocal); mfem::Vector localAction(f.densityFes->GetVSize()); localAction = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array densityDofs; mfem::Array enthalpyDofs; @@ -614,109 +510,76 @@ namespace mean_field::operators::kernels { mapping::VolumeMappingContext mappingContext; mapping::VolumeMappingVariation mappingVariation; - const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); - for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "The barotropic-closure displacement action " - "received a null element transformation." + transformation != nullptr, "The barotropic-closure displacement action " + "received a null element transformation." ); - if (transformation->Attribute == vacuumAttribute) { + if (!element_is_in_closure_support(transformation->Attribute)) { continue; } - const mfem::FiniteElement &densityElement = - *f.densityFes->GetFE(elementId); + const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId); - const mfem::FiniteElement &enthalpyElement = - *f.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId); - const mfem::FiniteElement &displacementElement = - *f.displacementFes->GetFE(elementId); + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); - const mfem::FiniteElement &compactificationElement = - *f.compactificationFes->GetFE(elementId); + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); - mfem::DofTransformation *densityDofTransformation = - f.densityFes->GetElementDofs(elementId, densityDofs); + mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs); - mfem::DofTransformation *enthalpyDofTransformation = - f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); + mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); mfem::DofTransformation *displacementDofTransformation = f.displacementFes->GetElementVDofs(elementId, displacementDofs); mfem::DofTransformation *compactificationDofTransformation = - f.compactificationFes->GetElementDofs( - elementId, compactificationDofs - ); + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity); baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy); - displacementLocal.GetSubVector( - displacementDofs, elementDisplacement - ); + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); - displacementVariationLocal.GetSubVector( - displacementDofs, elementDisplacementVariation - ); + displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation); - f.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (densityDofTransformation != nullptr) { - densityDofTransformation->InvTransformPrimal( - elementBaseDensity - ); + densityDofTransformation->InvTransformPrimal(elementBaseDensity); } if (enthalpyDofTransformation != nullptr) { - enthalpyDofTransformation->InvTransformPrimal( - elementBaseEnthalpy - ); + enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy); } if (displacementDofTransformation != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacement); - displacementDofTransformation->InvTransformPrimal( - elementDisplacementVariation - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); } if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } const mapping::ElementDisplacementData displacementData = - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); const mapping::ElementDisplacementData displacementVariationData = - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacementVariation - ); + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation); const mapping::ElementCompactificationData compactificationData( compactificationElement, elementCompactification ); const mapping::ElementMappingData mappingData{ - .displacement = displacementData, - .compactification = compactificationData + .displacement = displacementData, .compactification = compactificationData }; densityShape.SetSize(densityElement.GetDof()); @@ -724,74 +587,57 @@ namespace mean_field::operators::kernels { enthalpyShape.SetSize(enthalpyElement.GetDof()); elementAction.SetSize(densityElement.GetDof()); - elementAction = 0.0; + elementAction = 0.0; - const mfem::IntegrationRule &integrationRule = get_eos_rule( - f, barotrope, densityElement, enthalpyElement, *transformation - ); + const mfem::IntegrationRule &integrationRule = + get_eos_rule(f, barotrope, densityElement, enthalpyElement, *transformation); - for (int quadraturePoint = 0; - quadraturePoint < integrationRule.GetNPoints(); - ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadraturePoint); transformation->SetIntPoint(&integrationPoint); - const mapping::MappingStatus mappingStatus = - domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( mappingStatus == mapping::MappingStatus::valid, "The base mapping is invalid while applying " "the barotropic-closure displacement action. " "Element: " - << elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadraturePoint - << ", status: " << static_cast(mappingStatus) + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) ); - const mapping::MappingStatus variationStatus = - domainMapper.EvaluateVolumeVariation( - mappingData, displacementVariationData, *transformation, - integrationPoint, mappingContext, workspace, - mappingVariation - ); + const mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation( + mappingData, displacementVariationData, *transformation, integrationPoint, mappingContext, + workspace, mappingVariation + ); MFEM_VERIFY( variationStatus == mapping::MappingStatus::valid, "The mapping variation is invalid while " "applying the barotropic-closure " "displacement action. Element: " - << elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadraturePoint - << ", status: " << static_cast(variationStatus) + << elementId << ", attribute: " << transformation->Attribute << ", quadrature point: " + << quadraturePoint << ", status: " << static_cast(variationStatus) ); densityElement.CalcShape(integrationPoint, densityShape); enthalpyElement.CalcShape(integrationPoint, enthalpyShape); - const double densityValue = elementBaseDensity * densityShape; + const double densityValue = elementBaseDensity * densityShape; - const double enthalpyValue = - elementBaseEnthalpy * enthalpyShape; + const double enthalpyValue = elementBaseEnthalpy * enthalpyShape; - const double closureValue = - densityValue - - barotrope.density_from_enthalpy(enthalpyValue); + const double closureValue = densityValue - barotrope.density_from_enthalpy(enthalpyValue); - const double geometryActionValue = - closureValue * mappingVariation.weight_variation; + const double geometryActionValue = closureValue * mappingVariation.weight_variation; MFEM_VERIFY( - std::isfinite(closureValue) && - std::isfinite(geometryActionValue), + std::isfinite(closureValue) && std::isfinite(geometryActionValue), "The barotropic-closure displacement action " "encountered a non-finite quadrature value." ); @@ -808,4 +654,4 @@ namespace mean_field::operators::kernels { local_to_true(*f.densityFes, localAction, action); } -} // namespace mean_field::operators::kernels \ No newline at end of file +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp b/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp new file mode 100644 index 0000000..61f8eca --- /dev/null +++ b/libmeanfield/impl/operators/kernels/gravity_displacement_force_kernels.cpp @@ -0,0 +1,718 @@ +module; + +#include +#include +#include + +#include + +module mean_field; + +import :operators.kernels.gravity_displacement_force; + +namespace { + enum class GravityDisplacementForceAction { residual, density, gravityGradient, displacement, complete }; + + void true_to_local( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &trueVector, + mfem::Vector &localVector + ) { + MFEM_VERIFY( + trueVector.Size() == finiteElementSpace.GetTrueVSize(), + "The gravity-displacement-force true vector has the wrong size." + ); + + localVector.SetSize(finiteElementSpace.GetVSize()); + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->Mult(trueVector, localVector); + } else { + localVector = trueVector; + } + } + + void local_to_true( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &localVector, + mfem::Vector &trueVector + ) { + MFEM_VERIFY( + localVector.Size() == finiteElementSpace.GetVSize(), + "The gravity-displacement-force local vector has the wrong size." + ); + + trueVector.SetSize(finiteElementSpace.GetTrueVSize()); + trueVector = 0.0; + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->MultTranspose(localVector, trueVector); + } else { + trueVector = localVector; + } + } + + [[nodiscard]] int vector_dof_index( + const mfem::Ordering::Type ordering, + const int scalarDof, + const int component, + const int scalarDofCount, + const int dimension + ) { + if (ordering == mfem::Ordering::byNODES) { + return scalarDof + component * scalarDofCount; + } + + if (ordering == mfem::Ordering::byVDIM) { + return scalarDof * dimension + component; + } + + MFEM_ABORT( + "The gravity-displacement-force test space uses an unsupported " + "ordering." + ); + + return -1; + } + + [[nodiscard]] const mfem::IntegrationRule &get_gravity_force_rule( + const mean_field::fem::FEM &f, + const mfem::FiniteElement &densityElement, + const mfem::FiniteElement &gravityGradientElement, + const mfem::FiniteElement &displacementElement, + const mfem::ElementTransformation &transformation + ) { + using DisplacementField = mean_field::field::Field; + + MFEM_VERIFY( + densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder, + "The gravity-displacement-force density element does not match " + "the registered density field." + ); + + MFEM_VERIFY( + gravityGradientElement.GetOrder() == mean_field::field::Gravity::Flux::familyOrder + 1, + "The gravity-displacement-force RT element does not match the " + "registered gravity-gradient field." + ); + + MFEM_VERIFY( + displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder, + "The gravity-displacement-force test element does not match the " + "registered displacement field." + ); + + const mean_field::quadrature::Query query = + DisplacementField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); + + const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType()); + + MFEM_VERIFY( + rule.integration_rule != nullptr, "The quadrature policy did not return a gravity-displacement-" + "force integration rule." + ); + + return *rule.integration_rule; + } + + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + void validate_common_inputs( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &displacementTrue + ) { + MFEM_VERIFY(f.mesh != nullptr, "The gravity-displacement-force kernel requires a mesh."); + + MFEM_VERIFY( + f.densityFes != nullptr, "The gravity-displacement-force kernel requires the density " + "finite-element space." + ); + + MFEM_VERIFY( + f.gravityFluxFes != nullptr, "The gravity-displacement-force kernel requires the gravity-" + "gradient finite-element space." + ); + + MFEM_VERIFY( + f.displacementFes != nullptr, "The gravity-displacement-force kernel requires the displacement " + "finite-element space." + ); + + MFEM_VERIFY( + f.compactificationFes != nullptr && f.compactificationCoordinate != nullptr, + "The gravity-displacement-force kernel requires the " + "compactification coordinate." + ); + + MFEM_VERIFY( + f.quadratureFactory != nullptr, "The gravity-displacement-force kernel requires the quadrature " + "rule factory." + ); + + MFEM_VERIFY( + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), + "The gravity-displacement-force displacement vector has the " + "wrong size." + ); + + MFEM_VERIFY( + domainMapper.GetDimension() == f.mesh->Dimension(), + "The gravity-displacement-force mapper dimension does not match " + "the mesh dimension." + ); + + MFEM_VERIFY( + f.displacementFes->GetVDim() == f.mesh->Dimension(), + "The gravity-displacement-force displacement dimension does not " + "match the mesh dimension." + ); + + validate_finite_vector( + displacementTrue, "The gravity-displacement-force displacement contains a " + "non-finite value." + ); + } + + void validate_density( + const mean_field::fem::FEM &f, + const mfem::Vector &density, + const char *message + ) { + MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message); + validate_finite_vector(density, message); + } + + void validate_gravity_gradient( + const mean_field::fem::FEM &f, + const mfem::Vector &gravityGradient, + const char *message + ) { + MFEM_VERIFY(gravityGradient.Size() == f.gravityFluxFes->GetTrueVSize(), message); + + validate_finite_vector(gravityGradient, message); + } + + void apply_gravity_displacement_force_action( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const GravityDisplacementForceAction requestedAction, + const mfem::Vector *baseDensityTrue, + const mfem::Vector *densityVariationTrue, + const mfem::Vector *baseGravityGradientTrue, + const mfem::Vector *gravityGradientVariationTrue, + const mfem::Vector *displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + validate_common_inputs(f, domainMapper, displacementTrue); + + const bool needsBaseDensity = requestedAction == GravityDisplacementForceAction::residual || + requestedAction == GravityDisplacementForceAction::gravityGradient || + requestedAction == GravityDisplacementForceAction::displacement || + requestedAction == GravityDisplacementForceAction::complete; + + const bool needsDensityVariation = requestedAction == GravityDisplacementForceAction::density || + requestedAction == GravityDisplacementForceAction::complete; + + const bool needsBaseGravityGradient = requestedAction == GravityDisplacementForceAction::residual || + requestedAction == GravityDisplacementForceAction::density || + requestedAction == GravityDisplacementForceAction::displacement || + requestedAction == GravityDisplacementForceAction::complete; + + const bool needsGravityGradientVariation = requestedAction == GravityDisplacementForceAction::gravityGradient || + requestedAction == GravityDisplacementForceAction::complete; + + const bool needsDisplacementVariation = requestedAction == GravityDisplacementForceAction::displacement || + requestedAction == GravityDisplacementForceAction::complete; + + if (needsBaseDensity) { + MFEM_VERIFY( + baseDensityTrue != nullptr, "The gravity-displacement-force action requires a base " + "density." + ); + + validate_density(f, *baseDensityTrue, "The gravity-displacement-force base density is invalid."); + } + + if (needsDensityVariation) { + MFEM_VERIFY( + densityVariationTrue != nullptr, "The gravity-displacement-force action requires a density " + "variation." + ); + + validate_density( + f, *densityVariationTrue, + "The gravity-displacement-force density variation is " + "invalid." + ); + } + + if (needsBaseGravityGradient) { + MFEM_VERIFY( + baseGravityGradientTrue != nullptr, "The gravity-displacement-force action requires a base " + "gravity gradient." + ); + + validate_gravity_gradient( + f, *baseGravityGradientTrue, + "The gravity-displacement-force base gravity gradient is " + "invalid." + ); + } + + if (needsGravityGradientVariation) { + MFEM_VERIFY( + gravityGradientVariationTrue != nullptr, "The gravity-displacement-force action requires a gravity-" + "gradient variation." + ); + + validate_gravity_gradient( + f, *gravityGradientVariationTrue, + "The gravity-displacement-force gravity-gradient variation " + "is invalid." + ); + } + + if (needsDisplacementVariation) { + MFEM_VERIFY( + displacementVariationTrue != nullptr && + displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(), + "The gravity-displacement-force displacement variation is " + "invalid." + ); + + validate_finite_vector( + *displacementVariationTrue, "The gravity-displacement-force displacement variation " + "contains a non-finite value." + ); + } + + mfem::Vector baseDensityLocal; + mfem::Vector densityVariationLocal; + mfem::Vector baseGravityGradientLocal; + mfem::Vector gravityGradientVariationLocal; + mfem::Vector displacementLocal; + mfem::Vector displacementVariationLocal; + + if (needsBaseDensity) { + true_to_local(*f.densityFes, *baseDensityTrue, baseDensityLocal); + } + + if (needsDensityVariation) { + true_to_local(*f.densityFes, *densityVariationTrue, densityVariationLocal); + } + + if (needsBaseGravityGradient) { + true_to_local(*f.gravityFluxFes, *baseGravityGradientTrue, baseGravityGradientLocal); + } + + if (needsGravityGradientVariation) { + true_to_local(*f.gravityFluxFes, *gravityGradientVariationTrue, gravityGradientVariationLocal); + } + + true_to_local(*f.displacementFes, displacementTrue, displacementLocal); + + if (needsDisplacementVariation) { + true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal); + } + + mfem::Vector localAction(f.displacementFes->GetVSize()); + localAction = 0.0; + + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); + + mfem::Array densityDofs; + mfem::Array gravityGradientDofs; + mfem::Array displacementDofs; + mfem::Array compactificationDofs; + + mfem::Vector elementBaseDensity; + mfem::Vector elementDensityVariation; + mfem::Vector elementBaseGravityGradient; + mfem::Vector elementGravityGradientVariation; + mfem::Vector elementDisplacement; + mfem::Vector elementDisplacementVariation; + mfem::Vector elementCompactification; + mfem::Vector elementAction; + + mfem::Vector densityShape; + mfem::Vector displacementShape; + mfem::DenseMatrix gravityGradientShape; + + mfem::Vector baseGravityReferenceValue; + mfem::Vector gravityVariationReferenceValue; + mfem::Vector mappedBaseGravity; + mfem::Vector mappedGravityVariation; + mfem::Vector mappedGeometryVariation; + mfem::Vector forceValue; + + mean_field::mapping::VolumeMappingContext mappingContext; + mean_field::mapping::VolumeMappingVariation mappingVariation; + + const int dimension = f.mesh->Dimension(); + const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); + + const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering(); + + for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); + + MFEM_VERIFY( + transformation != nullptr, "The gravity-displacement-force kernel received a null " + "element transformation." + ); + + if (transformation->Attribute == vacuumAttribute) { + continue; + } + + const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId); + + const mfem::FiniteElement &gravityGradientElement = *f.gravityFluxFes->GetFE(elementId); + + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); + + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); + + mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs); + + mfem::DofTransformation *gravityGradientDofTransformation = + f.gravityFluxFes->GetElementVDofs(elementId, gravityGradientDofs); + + mfem::DofTransformation *displacementDofTransformation = + f.displacementFes->GetElementVDofs(elementId, displacementDofs); + + mfem::DofTransformation *compactificationDofTransformation = + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); + + if (needsBaseDensity) { + baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity); + } + + if (needsDensityVariation) { + densityVariationLocal.GetSubVector(densityDofs, elementDensityVariation); + } + + if (needsBaseGravityGradient) { + baseGravityGradientLocal.GetSubVector(gravityGradientDofs, elementBaseGravityGradient); + } + + if (needsGravityGradientVariation) { + gravityGradientVariationLocal.GetSubVector(gravityGradientDofs, elementGravityGradientVariation); + } + + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); + + if (needsDisplacementVariation) { + displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation); + } + + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); + + if (densityDofTransformation != nullptr) { + if (needsBaseDensity) { + densityDofTransformation->InvTransformPrimal(elementBaseDensity); + } + + if (needsDensityVariation) { + densityDofTransformation->InvTransformPrimal(elementDensityVariation); + } + } + + if (gravityGradientDofTransformation != nullptr) { + if (needsBaseGravityGradient) { + gravityGradientDofTransformation->InvTransformPrimal(elementBaseGravityGradient); + } + + if (needsGravityGradientVariation) { + gravityGradientDofTransformation->InvTransformPrimal(elementGravityGradientVariation); + } + } + + if (displacementDofTransformation != nullptr) { + displacementDofTransformation->InvTransformPrimal(elementDisplacement); + + if (needsDisplacementVariation) { + displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); + } + } + + if (compactificationDofTransformation != nullptr) { + compactificationDofTransformation->InvTransformPrimal(elementCompactification); + } + + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); + + const mean_field::mapping::ElementCompactificationData compactificationData( + compactificationElement, elementCompactification + ); + + const mean_field::mapping::ElementMappingData mappingData{ + .displacement = displacementData, .compactification = compactificationData + }; + + std::optional displacementVariationData; + + if (needsDisplacementVariation) { + displacementVariationData.emplace( + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacementElement, elementDisplacementVariation + ) + ); + } + + const int scalarDisplacementDofCount = displacementElement.GetDof(); + + MFEM_VERIFY( + displacementDofs.Size() == scalarDisplacementDofCount * dimension, + "The gravity-displacement-force element displacement vector " + "has the wrong size." + ); + + densityShape.SetSize(densityElement.GetDof()); + displacementShape.SetSize(scalarDisplacementDofCount); + gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension); + + baseGravityReferenceValue.SetSize(dimension); + gravityVariationReferenceValue.SetSize(dimension); + mappedBaseGravity.SetSize(dimension); + mappedGravityVariation.SetSize(dimension); + mappedGeometryVariation.SetSize(dimension); + forceValue.SetSize(dimension); + + elementAction.SetSize(displacementDofs.Size()); + elementAction = 0.0; + + const mfem::IntegrationRule &integrationRule = + get_gravity_force_rule(f, densityElement, gravityGradientElement, displacementElement, *transformation); + + for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex); + + transformation->SetIntPoint(&integrationPoint); + + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); + + MFEM_VERIFY( + mappingStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping failed in the gravity-displacement-" + "force kernel. Element: " + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadratureIndex << ", status: " << static_cast(mappingStatus) + ); + + if (needsDisplacementVariation) { + const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation( + mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext, + workspace, mappingVariation + ); + + MFEM_VERIFY( + variationStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping variation failed in the gravity-" + "displacement-force kernel. Element: " + << elementId << ", attribute: " << transformation->Attribute << ", quadrature point: " + << quadratureIndex << ", status: " << static_cast(variationStatus) + ); + } + + densityElement.CalcShape(integrationPoint, densityShape); + + displacementElement.CalcShape(integrationPoint, displacementShape); + + gravityGradientElement.CalcVShape(*transformation, gravityGradientShape); + + double baseDensityValue = 0.0; + double densityVariationValue = 0.0; + + if (needsBaseDensity) { + baseDensityValue = elementBaseDensity * densityShape; + } + + if (needsDensityVariation) { + densityVariationValue = elementDensityVariation * densityShape; + } + + if (needsBaseGravityGradient) { + gravityGradientShape.MultTranspose(elementBaseGravityGradient, baseGravityReferenceValue); + + mappingContext.mapping.mapping_jacobian.Mult(baseGravityReferenceValue, mappedBaseGravity); + } else { + mappedBaseGravity = 0.0; + } + + if (needsGravityGradientVariation) { + gravityGradientShape.MultTranspose(elementGravityGradientVariation, gravityVariationReferenceValue); + + mappingContext.mapping.mapping_jacobian.Mult( + gravityVariationReferenceValue, mappedGravityVariation + ); + } else { + mappedGravityVariation = 0.0; + } + + if (needsDisplacementVariation) { + mappingVariation.mapping.mapping_jacobian_variation.Mult( + baseGravityReferenceValue, mappedGeometryVariation + ); + } else { + mappedGeometryVariation = 0.0; + } + + forceValue = 0.0; + + if (requestedAction == GravityDisplacementForceAction::residual) { + forceValue.Add(baseDensityValue, mappedBaseGravity); + } else { + if (needsDensityVariation) { + forceValue.Add(densityVariationValue, mappedBaseGravity); + } + + if (needsGravityGradientVariation) { + forceValue.Add(baseDensityValue, mappedGravityVariation); + } + + if (needsDisplacementVariation) { + forceValue.Add(baseDensityValue, mappedGeometryVariation); + } + } + + /* + * If g_ref is the RT pullback, then + * + * g_phys = J_map g_ref / det(J_map), + * dV_phys = det(J_map) dV_ref. + * + * The determinant cancels exactly. Consequently the base + * integrand uses J_map g_ref and its geometry derivative uses + * delta(J_map) g_ref. This is algebraically identical to + * differentiating the Piola map and physical volume weight, + * but avoids a numerically pointless cancellation. + */ + const double referenceWeight = integrationPoint.weight * transformation->Weight(); + + forceValue *= referenceWeight; + + for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) { + for (int component = 0; component < dimension; ++component) { + const int vectorDof = vector_dof_index( + displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension + ); + + const double contribution = displacementShape(scalarDof) * forceValue(component); + + MFEM_VERIFY( + std::isfinite(contribution), "The gravity-displacement-force kernel " + "encountered a non-finite contribution." + ); + + elementAction(vectorDof) += contribution; + } + } + } + + if (displacementDofTransformation != nullptr) { + displacementDofTransformation->TransformDual(elementAction); + } + + localAction.AddElementVector(displacementDofs, elementAction); + } + + local_to_true(*f.displacementFes, localAction, actionTrue); + } +} // namespace + +namespace mean_field::operators::kernels { + void apply_gravity_displacement_force_residual( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &densityTrue, + const mfem::Vector &gravityGradientTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &residualTrue + ) { + apply_gravity_displacement_force_action( + f, domainMapper, GravityDisplacementForceAction::residual, &densityTrue, nullptr, &gravityGradientTrue, + nullptr, nullptr, displacementTrue, residualTrue + ); + } + + void apply_gravity_displacement_force_density_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_gravity_displacement_force_action( + f, domainMapper, GravityDisplacementForceAction::density, nullptr, &densityVariationTrue, + &baseGravityGradientTrue, nullptr, nullptr, displacementTrue, actionTrue + ); + } + + void apply_gravity_displacement_force_gradient_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &gravityGradientVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_gravity_displacement_force_action( + f, domainMapper, GravityDisplacementForceAction::gravityGradient, &baseDensityTrue, nullptr, nullptr, + &gravityGradientVariationTrue, nullptr, displacementTrue, actionTrue + ); + } + + void apply_gravity_displacement_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_gravity_displacement_force_action( + f, domainMapper, GravityDisplacementForceAction::displacement, &baseDensityTrue, nullptr, + &baseGravityGradientTrue, nullptr, &displacementVariationTrue, displacementTrue, actionTrue + ); + } + + void apply_gravity_displacement_force_complete_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &gravityGradientVariationTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_gravity_displacement_force_action( + f, domainMapper, GravityDisplacementForceAction::complete, &baseDensityTrue, &densityVariationTrue, + &baseGravityGradientTrue, &gravityGradientVariationTrue, &displacementVariationTrue, displacementTrue, + actionTrue + ); + } +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/impl/operators/kernels/gravity_kernels.cpp b/libmeanfield/impl/operators/kernels/gravity_kernels.cpp index 50d7c02..6fecbaa 100644 --- a/libmeanfield/impl/operators/kernels/gravity_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/gravity_kernels.cpp @@ -11,15 +11,11 @@ namespace { const mfem::Vector &true_vector, mfem::Vector &local_vector ) { - MFEM_VERIFY( - true_vector.Size() == finite_element_space.GetTrueVSize(), - "True vector has the wrong size." - ); + MFEM_VERIFY(true_vector.Size() == finite_element_space.GetTrueVSize(), "True vector has the wrong size."); local_vector.SetSize(finite_element_space.GetVSize()); - const mfem::Operator *prolongation = - finite_element_space.GetProlongationMatrix(); + const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(true_vector, local_vector); } else { @@ -32,16 +28,12 @@ namespace { const mfem::Vector &local_vector, mfem::Vector &true_vector ) { - MFEM_VERIFY( - local_vector.Size() == finite_element_space.GetVSize(), - "Local vector has the wrong size." - ); + MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size."); true_vector.SetSize(finite_element_space.GetTrueVSize()); - true_vector = 0.0; + true_vector = 0.0; - const mfem::Operator *prolongation = - finite_element_space.GetProlongationMatrix(); + const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(local_vector, true_vector); } else { @@ -66,8 +58,7 @@ namespace { const mean_field::mapping::DomainMapperStateless &domain_mapper, const mfem::ElementTransformation &transformation ) { - return transformation.Attribute == - domain_mapper.GetVacuumElementAttribute() + return transformation.Attribute == domain_mapper.GetVacuumElementAttribute() ? mean_field::quadrature::MappingKind::kelvin : mean_field::quadrature::MappingKind::general; } @@ -78,27 +69,22 @@ namespace { const mfem::FiniteElement &element, const mfem::ElementTransformation &transformation ) { - using GravityField = - mean_field::field::Field; + using GravityField = mean_field::field::Field; MFEM_VERIFY( - element.GetOrder() == - mean_field::field::Gravity::Flux::familyOrder + 1, + element.GetOrder() == mean_field::field::Gravity::Flux::familyOrder + 1, "The H(div) kernel element does not match the registered gravity " "flux." ); - const mean_field::quadrature::Query query = GravityField::make_query< - mean_field::field::Gravity::Form::HDivMass>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::ALL, - get_mapping_kind(domain_mapper, transformation) - ); + const mean_field::quadrature::Query query = + GravityField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::ALL, get_mapping_kind(domain_mapper, transformation) + ); - const auto resolution = - f.quadratureFactory->get(query, transformation.GetGeometryType()); + const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( - resolution.integration_rule != nullptr, - "The quadrature policy did not return an H(div) mass integration " - "rule." + resolution.integration_rule != nullptr, "The quadrature policy did not return an H(div) mass integration " + "rule." ); return *resolution.integration_rule; } @@ -109,35 +95,29 @@ namespace { const mfem::FiniteElement &potential_element, const mfem::ElementTransformation &transformation ) { - using GravityField = - mean_field::field::Field; + using GravityField = mean_field::field::Field; MFEM_VERIFY( - density_element.GetOrder() == - mean_field::field::Density::Scalar::familyOrder, + density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder, "The source-kernel trial element does not match the registered " "density " "field." ); MFEM_VERIFY( - potential_element.GetOrder() == - mean_field::field::Gravity::Potential::familyOrder, + potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder, "The source-kernel test element does not match the registered " "gravity " "potential." ); - const mean_field::quadrature::Query query = GravityField::make_query< - mean_field::field::Gravity::Form::SourceProjection>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general - ); + const mean_field::quadrature::Query query = + GravityField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); - const auto resolution = - f.quadratureFactory->get(query, transformation.GetGeometryType()); + const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( - resolution.integration_rule != nullptr, - "The quadrature policy did not return a gravity-source integration " - "rule." + resolution.integration_rule != nullptr, "The quadrature policy did not return a gravity-source integration " + "rule." ); return *resolution.integration_rule; } @@ -152,52 +132,39 @@ namespace mean_field::operators::kernels { mfem::Vector &action ) { MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "The H(div) mass kernel requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "The H(div) mass kernel requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( f.displacementFes != nullptr, "The H(div) mass kernel requires the " "displacement finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The H(div) mass kernel requires the compactification " - "finite-element " - "space." + f.compactificationFes != nullptr, "The H(div) mass kernel requires the compactification " + "finite-element " + "space." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The H(div) mass kernel requires the compactification field." - ); - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The H(div) mass kernel requires the quadrature rule factory." + f.compactificationCoordinate != nullptr, "The H(div) mass kernel requires the compactification field." ); + MFEM_VERIFY(f.quadratureFactory != nullptr, "The H(div) mass kernel requires the quadrature rule factory."); MFEM_VERIFY( gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(), "The gravity-gradient vector has the wrong size." ); MFEM_VERIFY( - displacement_true.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." + displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); mfem::Vector gravity_gradient_local; mfem::Vector displacement_local; - true_to_local( - *f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local - ); - true_to_local( - *f.displacementFes, displacement_true, displacement_local - ); + true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local); + true_to_local(*f.displacementFes, displacement_true, displacement_local); mfem::Vector local_action(f.gravityFluxFes->GetVSize()); local_action = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array gravity_dofs; mfem::Array displacement_dofs; @@ -214,64 +181,41 @@ namespace mean_field::operators::kernels { mfem::DenseMatrix mapped_mass_tensor; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - const mfem::FiniteElement &gravity_element = - *f.gravityFluxFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); mfem::DofTransformation *gravity_dof_transformation = f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs); mfem::DofTransformation *displacement_dof_transformation = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); - gravity_gradient_local.GetSubVector( - gravity_dofs, element_gravity_gradient - ); - displacement_local.GetSubVector( - displacement_dofs, element_displacement - ); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient); + displacement_local.GetSubVector(displacement_dofs, element_displacement); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (gravity_dof_transformation != nullptr) - gravity_dof_transformation->InvTransformPrimal( - element_gravity_gradient - ); + gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient); if (displacement_dof_transformation != nullptr) - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); if (compactification_dof_transformation != nullptr) - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); // const mapping::ElementDisplacementData // displacement_data(displacement_element, element_displacement, // mfem::Ordering::byVDIM); const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; const int gravity_dof_count = gravity_element.GetDof(); @@ -282,54 +226,40 @@ namespace mean_field::operators::kernels { mapped_gravity_gradient_value.SetSize(dimension); vector_shape.SetSize(gravity_dof_count, dimension); mapped_mass_tensor.SetSize(dimension); - element_action = 0.0; + element_action = 0.0; - const mfem::IntegrationRule &integration_rule = get_hdiv_mass_rule( - f, domain_mapper, gravity_element, *transformation - ); + const mfem::IntegrationRule &integration_rule = + get_hdiv_mass_rule(f, domain_mapper, gravity_element, *transformation); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); mapping::VolumeMappingContext mapping_context; - const mapping::MappingStatus status = - domain_mapper.EvaluateVolume( - mapping_data, *transformation, integration_point, - workspace, mapping_context - ); + const mapping::MappingStatus status = domain_mapper.EvaluateVolume( + mapping_data, *transformation, integration_point, workspace, mapping_context + ); MFEM_VERIFY( status == mapping::MappingStatus::valid, "Stateless mapping failed in the matrix-free H(div) mass " "kernel. " "Element: " - << element_id - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << q + << element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q << ", status: " << static_cast(status) ); gravity_element.CalcVShape(*transformation, vector_shape); - mapping::ComputeHDivMassTensor( - mapping_context.mapping, mapped_mass_tensor - ); + mapping::ComputeHDivMassTensor(mapping_context.mapping, mapped_mass_tensor); - vector_shape.MultTranspose( - element_gravity_gradient, gravity_gradient_value - ); - mapped_mass_tensor.Mult( - gravity_gradient_value, mapped_gravity_gradient_value - ); + vector_shape.MultTranspose(element_gravity_gradient, gravity_gradient_value); + mapped_mass_tensor.Mult(gravity_gradient_value, mapped_gravity_gradient_value); - const double weight = - integration_point.weight * transformation->Weight(); + const double weight = integration_point.weight * transformation->Weight(); for (int i = 0; i < gravity_dof_count; ++i) { double value = 0.0; for (int component = 0; component < dimension; ++component) - value += vector_shape(i, component) * - mapped_gravity_gradient_value(component); + value += vector_shape(i, component) * mapped_gravity_gradient_value(component); element_action(i) += weight * value; } } @@ -354,50 +284,35 @@ namespace mean_field::operators::kernels { "density finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "The gravity-source kernel requires the gravity-potential " - "finite-element space." + f.gravityPotentialFes != nullptr, "The gravity-source kernel requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The gravity-source kernel requires the " - "displacement finite-element space." + f.displacementFes != nullptr, "The gravity-source kernel requires the " + "displacement finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The gravity-source kernel requires the compactification " - "finite-element space." + f.compactificationFes != nullptr, "The gravity-source kernel requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The gravity-source kernel requires the compactification field." + f.compactificationCoordinate != nullptr, "The gravity-source kernel requires the compactification field." ); + MFEM_VERIFY(f.quadratureFactory != nullptr, "The gravity-source kernel requires the quadrature rule factory."); + MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(), "The density vector has the wrong size."); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The gravity-source kernel requires the quadrature rule factory." - ); - MFEM_VERIFY( - density_true.Size() == f.densityFes->GetTrueVSize(), - "The density vector has the wrong size." - ); - MFEM_VERIFY( - displacement_true.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." + displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); mfem::Vector density_local; mfem::Vector displacement_local; true_to_local(*f.densityFes, density_true, density_local); - true_to_local( - *f.displacementFes, displacement_true, displacement_local - ); + true_to_local(*f.displacementFes, displacement_true, displacement_local); mfem::Vector local_action(f.gravityPotentialFes->GetVSize()); local_action = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array density_dofs; mfem::Array potential_dofs; @@ -411,68 +326,46 @@ namespace mean_field::operators::kernels { mfem::Vector density_shape; mfem::Vector potential_shape; - const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); + const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); constexpr double source_scale = 4.0 * M_PI * utils::G; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); if (transformation->Attribute == vacuum_attribute) continue; - const mfem::FiniteElement &density_element = - *f.densityFes->GetFE(element_id); - const mfem::FiniteElement &potential_element = - *f.gravityPotentialFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &density_element = *f.densityFes->GetFE(element_id); + const mfem::FiniteElement &potential_element = *f.gravityPotentialFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); mfem::DofTransformation *density_dof_transformation = f.densityFes->GetElementDofs(element_id, density_dofs); mfem::DofTransformation *potential_dof_transformation = - f.gravityPotentialFes->GetElementDofs( - element_id, potential_dofs - ); + f.gravityPotentialFes->GetElementDofs(element_id, potential_dofs); mfem::DofTransformation *displacement_dof_transformation = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); density_local.GetSubVector(density_dofs, element_density); - displacement_local.GetSubVector( - displacement_dofs, element_displacement - ); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + displacement_local.GetSubVector(displacement_dofs, element_displacement); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (density_dof_transformation != nullptr) density_dof_transformation->InvTransformPrimal(element_density); if (displacement_dof_transformation != nullptr) - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); if (compactification_dof_transformation != nullptr) - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; const int density_dof_count = density_element.GetDof(); @@ -481,31 +374,25 @@ namespace mean_field::operators::kernels { density_shape.SetSize(density_dof_count); potential_shape.SetSize(potential_dof_count); element_action.SetSize(potential_dof_count); - element_action = 0.0; + element_action = 0.0; - const mfem::IntegrationRule &integration_rule = get_source_rule( - f, density_element, potential_element, *transformation - ); + const mfem::IntegrationRule &integration_rule = + get_source_rule(f, density_element, potential_element, *transformation); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); mapping::VolumeMappingContext mapping_context; - const mapping::MappingStatus status = - domain_mapper.EvaluateVolume( - mapping_data, *transformation, integration_point, - workspace, mapping_context - ); + const mapping::MappingStatus status = domain_mapper.EvaluateVolume( + mapping_data, *transformation, integration_point, workspace, mapping_context + ); MFEM_VERIFY( status == mapping::MappingStatus::valid, "Stateless mapping failed in the matrix-free " "gravity-source " "kernel. Element: " - << element_id - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << q + << element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q << ", status: " << static_cast(status) ); @@ -513,8 +400,7 @@ namespace mean_field::operators::kernels { potential_element.CalcShape(integration_point, potential_shape); const double density_value = element_density * density_shape; - const double weight = source_scale * density_value * - mapping_context.quadrature.weight; + const double weight = source_scale * density_value * mapping_context.quadrature.weight; for (int i = 0; i < potential_dof_count; ++i) element_action(i) += weight * potential_shape(i); @@ -536,46 +422,36 @@ namespace mean_field::operators::kernels { const mfem::Vector &displacement_variation_true, mfem::Vector &action_variation ) { + MFEM_VERIFY(f.mesh != nullptr, "The H(div) mass-variation kernel requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, - "The H(div) mass-variation kernel requires a mesh." + f.gravityFluxFes != nullptr, "The H(div) mass-variation kernel requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "The H(div) mass-variation kernel requires the " - "gravity-gradient finite-element space." + f.displacementFes != nullptr, "The H(div) mass-variation kernel requires " + "the displacement finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The H(div) mass-variation kernel requires " - "the displacement finite-element space." + f.compactificationFes != nullptr, "The H(div) mass-variation kernel requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The H(div) mass-variation kernel requires the compactification " - "finite-element space." + f.compactificationCoordinate != nullptr, "The H(div) mass-variation kernel requires the compactification " + "field." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The H(div) mass-variation kernel requires the compactification " - "field." - ); - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The H(div) mass-variation kernel requires the quadrature rule " - "factory." + f.quadratureFactory != nullptr, "The H(div) mass-variation kernel requires the quadrature rule " + "factory." ); MFEM_VERIFY( gravity_gradient_true.Size() == f.gravityFluxFes->GetTrueVSize(), "The gravity-gradient vector has the wrong size." ); MFEM_VERIFY( - displacement_true.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." + displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); MFEM_VERIFY( - displacement_variation_true.Size() == - f.displacementFes->GetTrueVSize(), + displacement_variation_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement-variation vector has the wrong size." ); MFEM_VERIFY( @@ -587,23 +463,14 @@ namespace mean_field::operators::kernels { mfem::Vector displacement_local; mfem::Vector displacement_variation_local; - true_to_local( - *f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local - ); - true_to_local( - *f.displacementFes, displacement_true, displacement_local - ); - true_to_local( - *f.displacementFes, displacement_variation_true, - displacement_variation_local - ); + true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local); + true_to_local(*f.displacementFes, displacement_true, displacement_local); + true_to_local(*f.displacementFes, displacement_variation_true, displacement_variation_local); mfem::Vector local_action(f.gravityFluxFes->GetVSize()); local_action = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array gravity_gradient_dofs; mfem::Array displacement_dofs; @@ -623,85 +490,52 @@ namespace mean_field::operators::kernels { const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - const mfem::FiniteElement &gravity_gradient_element = - *f.gravityFluxFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + const mfem::FiniteElement &gravity_gradient_element = *f.gravityFluxFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); MFEM_VERIFY( - transformation != nullptr, - "The H(div) mass-variation kernel " - "received a null element transformation." + transformation != nullptr, "The H(div) mass-variation kernel " + "received a null element transformation." ); mfem::DofTransformation *gravity_dof_transformation = - f.gravityFluxFes->GetElementVDofs( - element_id, gravity_gradient_dofs - ); + f.gravityFluxFes->GetElementVDofs(element_id, gravity_gradient_dofs); mfem::DofTransformation *displacement_dof_transformation = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); - gravity_gradient_local.GetSubVector( - gravity_gradient_dofs, element_gravity_gradient - ); - displacement_local.GetSubVector( - displacement_dofs, element_displacement - ); - displacement_variation_local.GetSubVector( - displacement_dofs, element_displacement_variation - ); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + gravity_gradient_local.GetSubVector(gravity_gradient_dofs, element_gravity_gradient); + displacement_local.GetSubVector(displacement_dofs, element_displacement); + displacement_variation_local.GetSubVector(displacement_dofs, element_displacement_variation); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (gravity_dof_transformation != nullptr) - gravity_dof_transformation->InvTransformPrimal( - element_gravity_gradient - ); + gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); - displacement_dof_transformation->InvTransformPrimal( - element_displacement_variation - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); + displacement_dof_transformation->InvTransformPrimal(element_displacement_variation); } if (compactification_dof_transformation != nullptr) - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); const mapping::ElementDisplacementData displacement_variation_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement_variation - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement_variation); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - const int gravity_gradient_dof_count = - gravity_gradient_element.GetDof(); - const int dimension = transformation->GetSpaceDim(); + const int gravity_gradient_dof_count = gravity_gradient_element.GetDof(); + const int dimension = transformation->GetSpaceDim(); element_action.SetSize(gravity_gradient_dof_count); element_action = 0.0; @@ -709,44 +543,34 @@ namespace mean_field::operators::kernels { gravity_gradient_value.SetSize(dimension); mass_tensor_variation_action.SetSize(dimension); - gravity_gradient_shape.SetSize( - gravity_gradient_dof_count, dimension - ); + gravity_gradient_shape.SetSize(gravity_gradient_dof_count, dimension); mass_tensor_variation.SetSize(dimension, dimension); - const mfem::IntegrationRule &integration_rule = get_hdiv_mass_rule( - f, domain_mapper, gravity_gradient_element, *transformation - ); + const mfem::IntegrationRule &integration_rule = + get_hdiv_mass_rule(f, domain_mapper, gravity_gradient_element, *transformation); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); mapping::VolumeMappingContext mapping_context; - const mapping::MappingStatus status = - domain_mapper.EvaluateVolume( - mapping_data, *transformation, integration_point, - workspace, mapping_context - ); + const mapping::MappingStatus status = domain_mapper.EvaluateVolume( + mapping_data, *transformation, integration_point, workspace, mapping_context + ); MFEM_VERIFY( status == mapping::MappingStatus::valid, "Stateless mapping failed in the matrix-free H(div) mass " "kernel. " "Element: " - << element_id - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << q + << element_id << ", attribute: " << transformation->Attribute << ", quadrature point: " << q << ", status: " << static_cast(status) ); mapping::VolumeMappingVariation mapping_variation; - const mapping::MappingStatus variation_status = - domain_mapper.EvaluateVolumeVariation( - mapping_data, displacement_variation_data, - *transformation, integration_point, mapping_context, - workspace, mapping_variation - ); + const mapping::MappingStatus variation_status = domain_mapper.EvaluateVolumeVariation( + mapping_data, displacement_variation_data, *transformation, integration_point, mapping_context, + workspace, mapping_variation + ); MFEM_VERIFY( variation_status == mapping::MappingStatus::valid, "The mapping variation is invalid while applying the " @@ -755,33 +579,20 @@ namespace mean_field::operators::kernels { ); mapping::ComputeHDivMassTensorVariation( - mapping_context.mapping, mapping_variation.mapping, - mass_tensor_variation + mapping_context.mapping, mapping_variation.mapping, mass_tensor_variation ); - gravity_gradient_element.CalcVShape( - *transformation, gravity_gradient_shape - ); - gravity_gradient_shape.MultTranspose( - element_gravity_gradient, gravity_gradient_value - ); - mass_tensor_variation.Mult( - gravity_gradient_value, mass_tensor_variation_action - ); - const double reference_weight = - integration_point.weight * transformation->Weight(); + gravity_gradient_element.CalcVShape(*transformation, gravity_gradient_shape); + gravity_gradient_shape.MultTranspose(element_gravity_gradient, gravity_gradient_value); + mass_tensor_variation.Mult(gravity_gradient_value, mass_tensor_variation_action); + const double reference_weight = integration_point.weight * transformation->Weight(); - gravity_gradient_shape.AddMult( - mass_tensor_variation_action, element_action, - reference_weight - ); + gravity_gradient_shape.AddMult(mass_tensor_variation_action, element_action, reference_weight); } if (gravity_dof_transformation != nullptr) gravity_dof_transformation->TransformDual(element_action); - local_action.AddElementVector( - gravity_gradient_dofs, element_action - ); + local_action.AddElementVector(gravity_gradient_dofs, element_action); } action_variation.SetSize(f.gravityFluxFes->GetTrueVSize()); @@ -797,48 +608,35 @@ namespace mean_field::operators::kernels { const mfem::Vector &displacement_variation_true, mfem::Vector &action_variation ) { + MFEM_VERIFY(f.mesh != nullptr, "The source-variation kernel requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, "The source-variation kernel requires a mesh." + f.densityFes != nullptr, "The source-variation kernel requires the density finite-element " + "space." ); MFEM_VERIFY( - f.densityFes != nullptr, - "The source-variation kernel requires the density finite-element " - "space." + f.gravityPotentialFes != nullptr, "The source-variation kernel requires the gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "The source-variation kernel requires the gravity-potential " - "finite-element space." + f.displacementFes != nullptr, "The source-variation kernel requires the " + "displacement finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The source-variation kernel requires the " - "displacement finite-element space." + f.compactificationFes != nullptr, "The source-variation kernel requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The source-variation kernel requires the compactification " - "finite-element space." + f.compactificationCoordinate != nullptr, "The source-variation kernel requires the compactification field." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The source-variation kernel requires the compactification field." + f.quadratureFactory != nullptr, "The source-variation kernel requires the quadrature rule factory." + ); + MFEM_VERIFY(density_true.Size() == f.densityFes->GetTrueVSize(), "The density vector has the wrong size."); + MFEM_VERIFY( + displacement_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement vector has the wrong size." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The source-variation kernel requires the quadrature rule factory." - ); - MFEM_VERIFY( - density_true.Size() == f.densityFes->GetTrueVSize(), - "The density vector has the wrong size." - ); - MFEM_VERIFY( - displacement_true.Size() == f.displacementFes->GetTrueVSize(), - "The displacement vector has the wrong size." - ); - MFEM_VERIFY( - displacement_variation_true.Size() == - f.displacementFes->GetTrueVSize(), + displacement_variation_true.Size() == f.displacementFes->GetTrueVSize(), "The displacement-variation vector has the wrong size." ); MFEM_VERIFY( @@ -851,20 +649,13 @@ namespace mean_field::operators::kernels { mfem::Vector displacement_variation_local; true_to_local(*f.densityFes, density_true, density_local); - true_to_local( - *f.displacementFes, displacement_true, displacement_local - ); - true_to_local( - *f.displacementFes, displacement_variation_true, - displacement_variation_local - ); + true_to_local(*f.displacementFes, displacement_true, displacement_local); + true_to_local(*f.displacementFes, displacement_variation_true, displacement_variation_local); mfem::Vector local_action(f.gravityPotentialFes->GetVSize()); local_action = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array density_dofs; mfem::Array potential_dofs; @@ -882,87 +673,58 @@ namespace mean_field::operators::kernels { mapping::VolumeMappingContext mapping_context; mapping::VolumeMappingVariation mapping_variation; - const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); + const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); constexpr double gravitational_source_scale = 4.0 * M_PI * utils::G; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); MFEM_VERIFY( - transformation != nullptr, - "The source-variation kernel received a null element " - "transformation." + transformation != nullptr, "The source-variation kernel received a null element " + "transformation." ); if (transformation->Attribute == vacuum_attribute) continue; - const mfem::FiniteElement &density_element = - *f.densityFes->GetFE(element_id); - const mfem::FiniteElement &potential_element = - *f.gravityPotentialFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &density_element = *f.densityFes->GetFE(element_id); + const mfem::FiniteElement &potential_element = *f.gravityPotentialFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); mfem::DofTransformation *density_dof_transformation = f.densityFes->GetElementDofs(element_id, density_dofs); mfem::DofTransformation *potential_dof_transformation = - f.gravityPotentialFes->GetElementDofs( - element_id, potential_dofs - ); + f.gravityPotentialFes->GetElementDofs(element_id, potential_dofs); mfem::DofTransformation *displacement_dof_transformation = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); density_local.GetSubVector(density_dofs, element_density); - displacement_local.GetSubVector( - displacement_dofs, element_displacement - ); - displacement_variation_local.GetSubVector( - displacement_dofs, element_displacement_variation - ); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + displacement_local.GetSubVector(displacement_dofs, element_displacement); + displacement_variation_local.GetSubVector(displacement_dofs, element_displacement_variation); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (density_dof_transformation != nullptr) density_dof_transformation->InvTransformPrimal(element_density); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); - displacement_dof_transformation->InvTransformPrimal( - element_displacement_variation - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); + displacement_dof_transformation->InvTransformPrimal(element_displacement_variation); } if (compactification_dof_transformation != nullptr) - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); const mapping::ElementDisplacementData displacement_variation_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement_variation - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement_variation); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; element_action.SetSize(potential_element.GetDof()); @@ -971,32 +733,26 @@ namespace mean_field::operators::kernels { density_shape.SetSize(density_element.GetDof()); potential_shape.SetSize(potential_element.GetDof()); - const mfem::IntegrationRule &integration_rule = get_source_rule( - f, density_element, potential_element, *transformation - ); + const mfem::IntegrationRule &integration_rule = + get_source_rule(f, density_element, potential_element, *transformation); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - const mapping::MappingStatus mapping_status = - domain_mapper.EvaluateVolume( - mapping_data, *transformation, integration_point, - workspace, mapping_context - ); + const mapping::MappingStatus mapping_status = domain_mapper.EvaluateVolume( + mapping_data, *transformation, integration_point, workspace, mapping_context + ); MFEM_VERIFY( mapping_status == mapping::MappingStatus::valid, "The base mapping is invalid while applying the source " "variation." ); - const mapping::MappingStatus variation_status = - domain_mapper.EvaluateVolumeVariation( - mapping_data, displacement_variation_data, - *transformation, integration_point, mapping_context, - workspace, mapping_variation - ); + const mapping::MappingStatus variation_status = domain_mapper.EvaluateVolumeVariation( + mapping_data, displacement_variation_data, *transformation, integration_point, mapping_context, + workspace, mapping_variation + ); MFEM_VERIFY( variation_status == mapping::MappingStatus::valid, @@ -1010,8 +766,7 @@ namespace mean_field::operators::kernels { const double density_value = density_shape * element_density; const double source_variation_value = - gravitational_source_scale * density_value * - mapping_variation.weight_variation; + gravitational_source_scale * density_value * mapping_variation.weight_variation; element_action.Add(source_variation_value, potential_shape); } @@ -1023,8 +778,6 @@ namespace mean_field::operators::kernels { action_variation.SetSize(f.gravityPotentialFes->GetTrueVSize()); action_variation = 0.0; - add_local_to_true( - *f.gravityPotentialFes, local_action, action_variation - ); + add_local_to_true(*f.gravityPotentialFes, local_action, action_variation); } } // namespace mean_field::operators::kernels diff --git a/libmeanfield/impl/operators/kernels/hydrostatic_equilibrium_kernels.cpp b/libmeanfield/impl/operators/kernels/hydrostatic_equilibrium_kernels.cpp index fd7d7b1..531e508 100644 --- a/libmeanfield/impl/operators/kernels/hydrostatic_equilibrium_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/hydrostatic_equilibrium_kernels.cpp @@ -16,15 +16,11 @@ namespace { const mfem::Vector &trueVector, mfem::Vector &localVector ) { - MFEM_VERIFY( - trueVector.Size() == finiteElementSpace.GetTrueVSize(), - "True vector has the wrong size." - ); + MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size."); localVector.SetSize(finiteElementSpace.GetVSize()); - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(trueVector, localVector); @@ -38,17 +34,13 @@ namespace { const mfem::Vector &localVector, mfem::Vector &trueVector ) { - MFEM_VERIFY( - localVector.Size() == finiteElementSpace.GetVSize(), - "Local vector has the wrong size." - ); + MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "Local vector has the wrong size."); trueVector.SetSize(finiteElementSpace.GetTrueVSize()); - trueVector = 0.0; + trueVector = 0.0; - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(localVector, trueVector); @@ -61,9 +53,7 @@ namespace { const mean_field::fem::FEM &f, const mean_field::mapping::DomainMapperStateless &domainMapper ) { - MFEM_VERIFY( - f.mesh != nullptr, "The hydrostatic kernel requires a mesh." - ); + MFEM_VERIFY(f.mesh != nullptr, "The hydrostatic kernel requires a mesh."); MFEM_VERIFY( f.enthalpyFes != nullptr, "The hydrostatic kernel requires the " @@ -71,9 +61,8 @@ namespace { ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "The hydrostatic kernel requires the " - "gravity-potential finite-element space." + f.gravityPotentialFes != nullptr, "The hydrostatic kernel requires the " + "gravity-potential finite-element space." ); MFEM_VERIFY( @@ -82,33 +71,28 @@ namespace { ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The hydrostatic kernel requires the " - "compactification finite-element space." + f.compactificationFes != nullptr, "The hydrostatic kernel requires the " + "compactification finite-element space." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The hydrostatic kernel requires the " - "compactification coordinate." + f.compactificationCoordinate != nullptr, "The hydrostatic kernel requires the " + "compactification coordinate." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The hydrostatic kernel requires the " - "quadrature-rule factory." + f.quadratureFactory != nullptr, "The hydrostatic kernel requires the " + "quadrature-rule factory." ); MFEM_VERIFY( - f.mesh->Dimension() == 3, - "The rigid-rotation hydrostatic kernel " - "currently requires a three-dimensional mesh." + f.mesh->Dimension() == 3, "The rigid-rotation hydrostatic kernel " + "currently requires a three-dimensional mesh." ); MFEM_VERIFY( - domainMapper.GetDimension() == f.mesh->Dimension(), - "The domain-mapper dimension does not match " - "the mesh dimension." + domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match " + "the mesh dimension." ); } @@ -118,69 +102,51 @@ namespace { const mfem::FiniteElement &potentialElement, const mfem::ElementTransformation &transformation ) { - using EnthalpyField = - mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; MFEM_VERIFY( - enthalpyElement.GetOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder, + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, "The hydrostatic test element does not match " "the registered enthalpy field." ); MFEM_VERIFY( - potentialElement.GetOrder() == - mean_field::field::Gravity::Potential::familyOrder, + potentialElement.GetOrder() == mean_field::field::Gravity::Potential::familyOrder, "The hydrostatic potential element does not " "match the registered gravity-potential field." ); - const auto enthalpyQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto enthalpyQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - const auto gravityQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumGravity>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto gravityQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - const auto rotationQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumRotation>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), std::array{2}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto rotationQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array{2}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - const auto constantQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumConstant>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto constantQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); int integrationOrder = 0; - const auto update_order = [&f, &transformation, &integrationOrder]( - const mean_field::quadrature::Query &query - ) { - const auto rule = f.quadratureFactory->get( - query, transformation.GetGeometryType() - ); + const auto update_order = [&f, &transformation, &integrationOrder](const mean_field::quadrature::Query &query) { + const auto rule = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( - rule.integration_rule != nullptr, - "The quadrature policy did not return " - "a hydrostatic-equilibrium rule." + rule.integration_rule != nullptr, "The quadrature policy did not return " + "a hydrostatic-equilibrium rule." ); - integrationOrder = - std::max(integrationOrder, rule.resolution.order); + integrationOrder = std::max(integrationOrder, rule.resolution.order); }; update_order(enthalpyQuery); @@ -188,9 +154,7 @@ namespace { update_order(rotationQuery); update_order(constantQuery); - return mfem::IntRules.Get( - transformation.GetGeometryType(), integrationOrder - ); + return mfem::IntRules.Get(transformation.GetGeometryType(), integrationOrder); } struct HydrostaticAssemblyRequest { @@ -219,81 +183,64 @@ namespace { validate_fem(f, domainMapper); MFEM_VERIFY( - displacementTrue.Size() == f.displacementFes->GetTrueVSize(), - "The hydrostatic displacement vector has " - "the wrong size." + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), "The hydrostatic displacement vector has " + "the wrong size." ); - MFEM_VERIFY( - std::isfinite(request.bernoulliConstant), - "The Bernoulli constant is non-finite." - ); + MFEM_VERIFY(std::isfinite(request.bernoulliConstant), "The Bernoulli constant is non-finite."); - MFEM_VERIFY( - std::isfinite(request.constantVariation), - "The Bernoulli-constant variation is non-finite." - ); + MFEM_VERIFY(std::isfinite(request.constantVariation), "The Bernoulli-constant variation is non-finite."); - const bool requiresBaseState = - request.buildResidual || - request.displacementVariationTrue != nullptr; + const bool requiresBaseState = request.buildResidual || request.displacementVariationTrue != nullptr; if (requiresBaseState) { MFEM_VERIFY( - request.rotation != nullptr, - "The hydrostatic residual or geometry " - "action requires the rotation model." + request.rotation != nullptr, "The hydrostatic residual or geometry " + "action requires the rotation model." ); MFEM_VERIFY( - request.baseEnthalpyTrue != nullptr, - "The hydrostatic residual or geometry " - "action requires the base enthalpy." + request.baseEnthalpyTrue != nullptr, "The hydrostatic residual or geometry " + "action requires the base enthalpy." ); MFEM_VERIFY( - request.basePotentialTrue != nullptr, - "The hydrostatic residual or geometry " - "action requires the base potential." + request.basePotentialTrue != nullptr, "The hydrostatic residual or geometry " + "action requires the base potential." ); } if (request.baseEnthalpyTrue != nullptr) { MFEM_VERIFY( - request.baseEnthalpyTrue->Size() == - f.enthalpyFes->GetTrueVSize(), + request.baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(), "The base enthalpy vector has the wrong size." ); } if (request.basePotentialTrue != nullptr) { MFEM_VERIFY( - request.basePotentialTrue->Size() == - f.gravityPotentialFes->GetTrueVSize(), + request.basePotentialTrue->Size() == f.gravityPotentialFes->GetTrueVSize(), "The base potential vector has the wrong size." ); } if (request.enthalpyVariationTrue != nullptr) { MFEM_VERIFY( - request.enthalpyVariationTrue->Size() == - f.enthalpyFes->GetTrueVSize(), + request.enthalpyVariationTrue->Size() == f.enthalpyFes->GetTrueVSize(), "The enthalpy variation has the wrong size." ); } if (request.potentialVariationTrue != nullptr) { MFEM_VERIFY( - request.potentialVariationTrue->Size() == - f.gravityPotentialFes->GetTrueVSize(), + request.potentialVariationTrue->Size() == f.gravityPotentialFes->GetTrueVSize(), "The potential variation has the wrong size." ); } if (request.displacementVariationTrue != nullptr) { MFEM_VERIFY( - request.displacementVariationTrue->Size() == - f.displacementFes->GetTrueVSize(), + request.displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(), "The displacement variation has the wrong size." ); } @@ -308,46 +255,30 @@ namespace { mfem::Vector displacementVariationLocal; if (request.baseEnthalpyTrue != nullptr) { - true_to_local( - *f.enthalpyFes, *request.baseEnthalpyTrue, baseEnthalpyLocal - ); + true_to_local(*f.enthalpyFes, *request.baseEnthalpyTrue, baseEnthalpyLocal); } if (request.basePotentialTrue != nullptr) { - true_to_local( - *f.gravityPotentialFes, *request.basePotentialTrue, - basePotentialLocal - ); + true_to_local(*f.gravityPotentialFes, *request.basePotentialTrue, basePotentialLocal); } if (request.enthalpyVariationTrue != nullptr) { - true_to_local( - *f.enthalpyFes, *request.enthalpyVariationTrue, - enthalpyVariationLocal - ); + true_to_local(*f.enthalpyFes, *request.enthalpyVariationTrue, enthalpyVariationLocal); } if (request.potentialVariationTrue != nullptr) { - true_to_local( - *f.gravityPotentialFes, *request.potentialVariationTrue, - potentialVariationLocal - ); + true_to_local(*f.gravityPotentialFes, *request.potentialVariationTrue, potentialVariationLocal); } if (request.displacementVariationTrue != nullptr) { - true_to_local( - *f.displacementFes, *request.displacementVariationTrue, - displacementVariationLocal - ); + true_to_local(*f.displacementFes, *request.displacementVariationTrue, displacementVariationLocal); } mfem::Vector localResult(f.enthalpyFes->GetVSize()); localResult = 0.0; - mean_field::mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); mfem::Array enthalpyDofs; mfem::Array potentialDofs; @@ -369,33 +300,26 @@ namespace { const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "The hydrostatic kernel received a null " - "element transformation." + transformation != nullptr, "The hydrostatic kernel received a null " + "element transformation." ); if (transformation->Attribute == vacuumAttribute) { continue; } - const mfem::FiniteElement &enthalpyElement = - *f.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId); - const mfem::FiniteElement &potentialElement = - *f.gravityPotentialFes->GetFE(elementId); + const mfem::FiniteElement &potentialElement = *f.gravityPotentialFes->GetFE(elementId); - const mfem::FiniteElement &displacementElement = - *f.displacementFes->GetFE(elementId); + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); - const mfem::FiniteElement &compactificationElement = - *f.compactificationFes->GetFE(elementId); + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); - mfem::DofTransformation *enthalpyDofTransformation = - f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); + mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); mfem::DofTransformation *potentialDofTransformation = f.gravityPotentialFes->GetElementDofs(elementId, potentialDofs); @@ -404,119 +328,82 @@ namespace { f.displacementFes->GetElementVDofs(elementId, displacementDofs); mfem::DofTransformation *compactificationDofTransformation = - f.compactificationFes->GetElementDofs( - elementId, compactificationDofs - ); + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); - displacementLocal.GetSubVector( - displacementDofs, elementDisplacement - ); + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); - f.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (request.baseEnthalpyTrue != nullptr) { - baseEnthalpyLocal.GetSubVector( - enthalpyDofs, elementBaseEnthalpy - ); + baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy); } if (request.basePotentialTrue != nullptr) { - basePotentialLocal.GetSubVector( - potentialDofs, elementBasePotential - ); + basePotentialLocal.GetSubVector(potentialDofs, elementBasePotential); } if (request.enthalpyVariationTrue != nullptr) { - enthalpyVariationLocal.GetSubVector( - enthalpyDofs, elementEnthalpyVariation - ); + enthalpyVariationLocal.GetSubVector(enthalpyDofs, elementEnthalpyVariation); } if (request.potentialVariationTrue != nullptr) { - potentialVariationLocal.GetSubVector( - potentialDofs, elementPotentialVariation - ); + potentialVariationLocal.GetSubVector(potentialDofs, elementPotentialVariation); } if (request.displacementVariationTrue != nullptr) { - displacementVariationLocal.GetSubVector( - displacementDofs, elementDisplacementVariation - ); + displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation); } if (enthalpyDofTransformation != nullptr) { if (request.baseEnthalpyTrue != nullptr) { - enthalpyDofTransformation->InvTransformPrimal( - elementBaseEnthalpy - ); + enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy); } if (request.enthalpyVariationTrue != nullptr) { - enthalpyDofTransformation->InvTransformPrimal( - elementEnthalpyVariation - ); + enthalpyDofTransformation->InvTransformPrimal(elementEnthalpyVariation); } } if (potentialDofTransformation != nullptr) { if (request.basePotentialTrue != nullptr) { - potentialDofTransformation->InvTransformPrimal( - elementBasePotential - ); + potentialDofTransformation->InvTransformPrimal(elementBasePotential); } if (request.potentialVariationTrue != nullptr) { - potentialDofTransformation->InvTransformPrimal( - elementPotentialVariation - ); + potentialDofTransformation->InvTransformPrimal(elementPotentialVariation); } } if (displacementDofTransformation != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacement); if (request.displacementVariationTrue != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacementVariation - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); } } if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } - const mean_field::mapping::ElementDisplacementData - displacementData = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ); + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); - const mean_field::mapping::ElementCompactificationData - compactificationData( - compactificationElement, elementCompactification - ); + const mean_field::mapping::ElementCompactificationData compactificationData( + compactificationElement, elementCompactification + ); const mean_field::mapping::ElementMappingData mappingData{ - .displacement = displacementData, - .compactification = compactificationData + .displacement = displacementData, .compactification = compactificationData }; - std::optional - displacementVariationData; + std::optional displacementVariationData; if (request.displacementVariationTrue != nullptr) { displacementVariationData.emplace( - mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacementVariation - ) + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacementElement, elementDisplacementVariation + ) ); } @@ -528,32 +415,25 @@ namespace { potentialShape.SetSize(potentialElement.GetDof()); - const mfem::IntegrationRule &integrationRule = get_hydrostatic_rule( - f, enthalpyElement, potentialElement, *transformation - ); + const mfem::IntegrationRule &integrationRule = + get_hydrostatic_rule(f, enthalpyElement, potentialElement, *transformation); - for (int quadraturePoint = 0; - quadraturePoint < integrationRule.GetNPoints(); - ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadraturePoint); transformation->SetIntPoint(&integrationPoint); mean_field::mapping::VolumeMappingContext mappingContext; - const mean_field::mapping::MappingStatus mappingStatus = - domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( mappingStatus == mean_field::mapping::MappingStatus::valid, "The base mapping is invalid in the " "hydrostatic kernel. Element: " - << elementId - << ", quadrature point: " << quadraturePoint + << elementId << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) ); @@ -564,27 +444,18 @@ namespace { double baseIntegrand = 0.0; if (requiresBaseState) { - const double enthalpyValue = - elementBaseEnthalpy * enthalpyShape; + const double enthalpyValue = elementBaseEnthalpy * enthalpyShape; - const double potentialValue = - elementBasePotential * potentialShape; + const double potentialValue = elementBasePotential * potentialShape; const double rotationPotential = - request.rotation->potential( - mappingContext.mapping.physical_position - ); + request.rotation->potential(mappingContext.mapping.physical_position); - baseIntegrand = enthalpyValue + potentialValue - - rotationPotential - - request.bernoulliConstant; + baseIntegrand = enthalpyValue + potentialValue - rotationPotential - request.bernoulliConstant; } if (request.buildResidual) { - elementResult.Add( - mappingContext.quadrature.weight * baseIntegrand, - enthalpyShape - ); + elementResult.Add(mappingContext.quadrature.weight * baseIntegrand, enthalpyShape); continue; } @@ -592,45 +463,35 @@ namespace { double materialVariation = -request.constantVariation; if (request.enthalpyVariationTrue != nullptr) { - materialVariation += - elementEnthalpyVariation * enthalpyShape; + materialVariation += elementEnthalpyVariation * enthalpyShape; } if (request.potentialVariationTrue != nullptr) { - materialVariation += - elementPotentialVariation * potentialShape; + materialVariation += elementPotentialVariation * potentialShape; } - double weightedVariation = - mappingContext.quadrature.weight * materialVariation; + double weightedVariation = mappingContext.quadrature.weight * materialVariation; if (request.displacementVariationTrue != nullptr) { - mean_field::mapping::VolumeMappingVariation - mappingVariation; + mean_field::mapping::VolumeMappingVariation mappingVariation; - const mean_field::mapping::MappingStatus variationStatus = - domainMapper.EvaluateVolumeVariation( - mappingData, *displacementVariationData, - *transformation, integrationPoint, mappingContext, - workspace, mappingVariation - ); + const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation( + mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext, + workspace, mappingVariation + ); MFEM_VERIFY( - variationStatus == - mean_field::mapping::MappingStatus::valid, + variationStatus == mean_field::mapping::MappingStatus::valid, "The mapping variation is invalid " "in the hydrostatic kernel." ); - const double rotationVariation = - request.rotation->potential_directional_derivative( - mappingContext.mapping.physical_position, - mappingVariation.mapping.physical_position_variation - ); + const double rotationVariation = request.rotation->potential_directional_derivative( + mappingContext.mapping.physical_position, mappingVariation.mapping.physical_position_variation + ); - weightedVariation += - baseIntegrand * mappingVariation.weight_variation - - rotationVariation * mappingContext.quadrature.weight; + weightedVariation += baseIntegrand * mappingVariation.weight_variation - + rotationVariation * mappingContext.quadrature.weight; } elementResult.Add(weightedVariation, enthalpyShape); @@ -666,9 +527,7 @@ namespace mean_field::operators::kernels { request.bernoulliConstant = bernoulliConstant; request.buildResidual = true; - assemble_hydrostatic_form( - f, domainMapper, displacementTrue, request, residual - ); + assemble_hydrostatic_form(f, domainMapper, displacementTrue, request, residual); } void apply_hydrostatic_equilibrium_enthalpy_action( @@ -682,9 +541,7 @@ namespace mean_field::operators::kernels { request.enthalpyVariationTrue = &enthalpyVariationTrue; - assemble_hydrostatic_form( - f, domainMapper, displacementTrue, request, action - ); + assemble_hydrostatic_form(f, domainMapper, displacementTrue, request, action); } void apply_hydrostatic_equilibrium_potential_action( @@ -698,9 +555,7 @@ namespace mean_field::operators::kernels { request.potentialVariationTrue = &potentialVariationTrue; - assemble_hydrostatic_form( - f, domainMapper, displacementTrue, request, action - ); + assemble_hydrostatic_form(f, domainMapper, displacementTrue, request, action); } void apply_hydrostatic_equilibrium_constant_action( @@ -714,9 +569,7 @@ namespace mean_field::operators::kernels { request.constantVariation = constantVariation; - assemble_hydrostatic_form( - f, domainMapper, displacementTrue, request, action - ); + assemble_hydrostatic_form(f, domainMapper, displacementTrue, request, action); } void apply_hydrostatic_equilibrium_displacement_action( @@ -738,9 +591,7 @@ namespace mean_field::operators::kernels { request.displacementVariationTrue = &displacementVariationTrue; request.bernoulliConstant = baseBernoulliConstant; - assemble_hydrostatic_form( - f, domainMapper, baseDisplacementTrue, request, action - ); + assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request, action); } void apply_hydrostatic_equilibrium_action( @@ -768,8 +619,6 @@ namespace mean_field::operators::kernels { request.bernoulliConstant = baseBernoulliConstant; request.constantVariation = constantVariation; - assemble_hydrostatic_form( - f, domainMapper, baseDisplacementTrue, request, action - ); + assemble_hydrostatic_form(f, domainMapper, baseDisplacementTrue, request, action); } } // namespace mean_field::operators::kernels \ No newline at end of file diff --git a/libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp b/libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp index a97ce6b..f82a654 100644 --- a/libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp +++ b/libmeanfield/impl/operators/kernels/pressure_force_kernels.cpp @@ -3,6 +3,7 @@ module; #include #include #include +#include #include @@ -11,20 +12,20 @@ module mean_field; import :operators.kernels.pressure_force; namespace { + enum class PressureForceAction { residual, enthalpy, displacement }; + void true_to_local( const mfem::ParFiniteElementSpace &finiteElementSpace, const mfem::Vector &trueVector, mfem::Vector &localVector ) { MFEM_VERIFY( - trueVector.Size() == finiteElementSpace.GetTrueVSize(), - "The pressure-force true vector has the wrong size." + trueVector.Size() == finiteElementSpace.GetTrueVSize(), "The pressure-force true vector has the wrong size." ); localVector.SetSize(finiteElementSpace.GetVSize()); - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(trueVector, localVector); @@ -39,15 +40,13 @@ namespace { mfem::Vector &trueVector ) { MFEM_VERIFY( - localVector.Size() == finiteElementSpace.GetVSize(), - "The pressure-force local vector has the wrong size." + localVector.Size() == finiteElementSpace.GetVSize(), "The pressure-force local vector has the wrong size." ); trueVector.SetSize(finiteElementSpace.GetTrueVSize()); - trueVector = 0.0; + trueVector = 0.0; - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(localVector, trueVector); @@ -68,15 +67,14 @@ namespace { } if (ordering == mfem::Ordering::byVDIM) { - return component + scalarDof * dimension; + return scalarDof * dimension + component; } + MFEM_ABORT("The displacement space uses an unsupported ordering."); return -1; } - [[nodiscard]] int get_pressure_extra_order( - const mean_field::physics::PolytropicBarotrope &barotrope - ) { + [[nodiscard]] int get_pressure_extra_order(const mean_field::eos::Polytrope &barotrope) { /* * Pressure has the enthalpy dependence * @@ -87,15 +85,11 @@ namespace { * contribution is therefore n times that order. */ const double extraOrder = - barotrope.polytropic_index() * - static_cast( - mean_field::field::Enthalpy::Scalar::familyOrder - ); + barotrope.polytropic_index() * static_cast(mean_field::field::Enthalpy::Scalar::familyOrder); MFEM_VERIFY( std::isfinite(extraOrder) && extraOrder >= 0.0 && - extraOrder <= - static_cast(std::numeric_limits::max()), + extraOrder <= static_cast(std::numeric_limits::max()), "The pressure EOS effective polynomial order is invalid." ); @@ -104,44 +98,37 @@ namespace { [[nodiscard]] const mfem::IntegrationRule &get_pressure_force_rule( const mean_field::fem::FEM &f, - const mean_field::physics::PolytropicBarotrope &barotrope, + const mean_field::eos::Polytrope &barotrope, const mfem::FiniteElement &enthalpyElement, const mfem::FiniteElement &displacementElement, const mfem::ElementTransformation &transformation ) { - using EnthalpyField = - mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; MFEM_VERIFY( - enthalpyElement.GetOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder, + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, "The pressure-force enthalpy element does not match the " "registered enthalpy field." ); MFEM_VERIFY( - displacementElement.GetOrder() == - mean_field::field::Displacement::Vector::familyOrder, + displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder, "The pressure-force test element does not match the " "registered displacement field." ); - const mean_field::quadrature::Query query = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::PressureForce>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), - std::array{get_pressure_extra_order(barotrope)}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general - ); + const mean_field::quadrature::Query query = + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), + std::array{get_pressure_extra_order(barotrope)}, mean_field::utils::DOMAINS::STELLAR, + mean_field::quadrature::MappingKind::general + ); - const mean_field::quadrature::MfemRule rule = - f.quadratureFactory->get(query, transformation.GetGeometryType()); + const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( - rule.integration_rule != nullptr, - "The quadrature policy did not return a pressure-force " - "integration rule." + rule.integration_rule != nullptr, "The quadrature policy did not return a pressure-force " + "integration rule." ); return *rule.integration_rule; @@ -153,38 +140,31 @@ namespace { const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue ) { + MFEM_VERIFY(f.mesh != nullptr, "The pressure-force kernel requires a mesh."); + MFEM_VERIFY( - f.mesh != nullptr, "The pressure-force kernel requires a mesh." + f.enthalpyFes != nullptr, "The pressure-force kernel requires the enthalpy " + "finite-element space." ); MFEM_VERIFY( - f.enthalpyFes != nullptr, - "The pressure-force kernel requires the enthalpy " - "finite-element space." + f.displacementFes != nullptr, "The pressure-force kernel requires the displacement " + "finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "The pressure-force kernel requires the displacement " - "finite-element space." + f.compactificationFes != nullptr, "The pressure-force kernel requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "The pressure-force kernel requires the compactification " - "finite-element space." + f.compactificationCoordinate != nullptr, "The pressure-force kernel requires the compactification " + "coordinate." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "The pressure-force kernel requires the compactification " - "coordinate." - ); - - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "The pressure-force kernel requires the quadrature " - "rule factory." + f.quadratureFactory != nullptr, "The pressure-force kernel requires the quadrature " + "rule factory." ); MFEM_VERIFY( @@ -204,73 +184,105 @@ namespace { ); MFEM_VERIFY( - f.displacementFes->GetVDim() == f.mesh->Dimension(), - "The displacement vector dimension does not match the " - "mesh dimension." + f.displacementFes->GetVDim() == f.mesh->Dimension(), "The displacement vector dimension does not match the " + "mesh dimension." ); + /* + * ElementDisplacementDataFromElementVDofs currently consumes the + * registered byNODES layout. Keep this explicit so a future + * registry change fails immediately rather than silently + * corrupting the geometry. + */ MFEM_VERIFY( f.displacementFes->GetOrdering() == mfem::Ordering::byNODES, "The pressure-force kernel requires the registered byNODES " "displacement ordering." ); } -} // namespace -namespace mean_field::operators::kernels { - void apply_pressure_force_residual( - const fem::FEM &f, - const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, - const mfem::Vector &enthalpyTrue, + void apply_pressure_force_action( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const mean_field::eos::Polytrope &barotrope, + const PressureForceAction pressureForceAction, + const mfem::Vector &baseEnthalpyTrue, + const mfem::Vector *enthalpyVariationTrue, + const mfem::Vector *displacementVariationTrue, const mfem::Vector &displacementTrue, - mfem::Vector &residualTrue + mfem::Vector &actionTrue ) { - validate_inputs(f, domainMapper, enthalpyTrue, displacementTrue); + validate_inputs(f, domainMapper, baseEnthalpyTrue, displacementTrue); - mfem::Vector enthalpyLocal; + if (pressureForceAction == PressureForceAction::enthalpy) { + MFEM_VERIFY( + enthalpyVariationTrue != nullptr && enthalpyVariationTrue->Size() == f.enthalpyFes->GetTrueVSize(), + "The pressure-force enthalpy variation has the wrong size." + ); + } + + if (pressureForceAction == PressureForceAction::displacement) { + MFEM_VERIFY( + displacementVariationTrue != nullptr && + displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(), + "The pressure-force displacement variation has the wrong " + "size." + ); + } + + mfem::Vector baseEnthalpyLocal; + mfem::Vector enthalpyVariationLocal; mfem::Vector displacementLocal; + mfem::Vector displacementVariationLocal; - true_to_local(*f.enthalpyFes, enthalpyTrue, enthalpyLocal); + true_to_local(*f.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal); + + if (enthalpyVariationTrue != nullptr) { + true_to_local(*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal); + } true_to_local(*f.displacementFes, displacementTrue, displacementLocal); - mfem::Vector localResidual(f.displacementFes->GetVSize()); - localResidual = 0.0; + if (displacementVariationTrue != nullptr) { + true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal); + } - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mfem::Vector localAction(f.displacementFes->GetVSize()); + localAction = 0.0; - mfem::Array enthalpyDofs; + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); + + mfem::Array enthalpyDofsofs; mfem::Array displacementDofs; mfem::Array compactificationDofs; - mfem::Vector elementEnthalpy; + mfem::Vector elementBaseEnthalpy; + mfem::Vector elementEnthalpyVariation; mfem::Vector elementDisplacement; + mfem::Vector elementDisplacementVariation; mfem::Vector elementCompactification; - mfem::Vector elementResidual; + mfem::Vector elementAction; mfem::Vector enthalpyShape; + mfem::Array enthalpyDofs; + mfem::DenseMatrix displacementDShapeReference; mfem::DenseMatrix displacementDShapePhysical; + mfem::DenseMatrix displacementDShapePhysicalVariation; - mapping::VolumeMappingContext mappingContext; + mean_field::mapping::VolumeMappingContext mappingContext; - const int dimension = f.mesh->Dimension(); - const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); + const int dimension = f.mesh->Dimension(); + const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); - const mfem::Ordering::Type displacementOrdering = - f.displacementFes->GetOrdering(); + const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering(); for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "The pressure-force kernel received a null element " - "transformation." + transformation != nullptr, "The pressure-force kernel received a null element " + "transformation." ); /* @@ -281,128 +293,131 @@ namespace mean_field::operators::kernels { continue; } - const mfem::FiniteElement &enthalpyElement = - *f.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId); - const mfem::FiniteElement &displacementElement = - *f.displacementFes->GetFE(elementId); + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); - const mfem::FiniteElement &compactificationElement = - *f.compactificationFes->GetFE(elementId); + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); - mfem::DofTransformation *enthalpyDofTransformation = - f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); + mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); mfem::DofTransformation *displacementDofTransformation = f.displacementFes->GetElementVDofs(elementId, displacementDofs); mfem::DofTransformation *compactificationDofTransformation = - f.compactificationFes->GetElementDofs( - elementId, compactificationDofs - ); + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); - enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy); + baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy); - displacementLocal.GetSubVector( - displacementDofs, elementDisplacement - ); + if (enthalpyVariationTrue != nullptr) { + enthalpyVariationLocal.GetSubVector(enthalpyDofs, elementEnthalpyVariation); + } - f.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); + + if (displacementVariationTrue != nullptr) { + displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation); + } + + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (enthalpyDofTransformation != nullptr) { - enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy); + enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy); + + if (enthalpyVariationTrue != nullptr) { + enthalpyDofTransformation->InvTransformPrimal(elementEnthalpyVariation); + } } if (displacementDofTransformation != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacement); + + if (displacementVariationTrue != nullptr) { + displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); + } } if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } - const mapping::ElementDisplacementData displacementData = - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ); + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); - const mapping::ElementCompactificationData compactificationData( + const mean_field::mapping::ElementCompactificationData compactificationData( compactificationElement, elementCompactification ); - const mapping::ElementMappingData mappingData{ - .displacement = displacementData, - .compactification = compactificationData + const mean_field::mapping::ElementMappingData mappingData{ + .displacement = displacementData, .compactification = compactificationData }; + std::optional displacementVariationData; + + if (displacementVariationTrue != nullptr) { + displacementVariationData.emplace( + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacementElement, elementDisplacementVariation + ) + ); + } + const int scalarDisplacementDofCount = displacementElement.GetDof(); MFEM_VERIFY( - displacementDofs.Size() == - scalarDisplacementDofCount * dimension, + displacementDofs.Size() == scalarDisplacementDofCount * dimension, "The pressure-force element displacement vector has " "the wrong size." ); enthalpyShape.SetSize(enthalpyElement.GetDof()); - displacementDShapeReference.SetSize( - scalarDisplacementDofCount, dimension - ); + displacementDShapeReference.SetSize(scalarDisplacementDofCount, dimension); - displacementDShapePhysical.SetSize( - scalarDisplacementDofCount, dimension - ); + displacementDShapePhysical.SetSize(scalarDisplacementDofCount, dimension); - elementResidual.SetSize(displacementDofs.Size()); - elementResidual = 0.0; + displacementDShapePhysicalVariation.SetSize(scalarDisplacementDofCount, dimension); + + elementAction.SetSize(displacementDofs.Size()); + elementAction = 0.0; const mfem::IntegrationRule &integrationRule = - get_pressure_force_rule( - f, barotrope, enthalpyElement, displacementElement, - *transformation - ); + get_pressure_force_rule(f, barotrope, enthalpyElement, displacementElement, *transformation); - for (int quadratureIndex = 0; - quadratureIndex < integrationRule.GetNPoints(); - ++quadratureIndex) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(quadratureIndex); + for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex); transformation->SetIntPoint(&integrationPoint); - const mapping::MappingStatus mappingStatus = - domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( - mappingStatus == mapping::MappingStatus::valid, + mappingStatus == mean_field::mapping::MappingStatus::valid, "Stateless mapping failed in the pressure-force " "kernel. Element: " - << elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadratureIndex - << ", status: " << static_cast(mappingStatus) + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadratureIndex << ", status: " << static_cast(mappingStatus) ); enthalpyElement.CalcShape(integrationPoint, enthalpyShape); - const double enthalpyValue = elementEnthalpy * enthalpyShape; + const double enthalpyValue = elementBaseEnthalpy * enthalpyShape; - const double pressureValue = - barotrope.pressure_from_enthalpy(enthalpyValue); + double pressureFactor = 0.0; - displacementElement.CalcDShape( - integrationPoint, displacementDShapeReference - ); + if (pressureForceAction == PressureForceAction::residual || + pressureForceAction == PressureForceAction::displacement) { + pressureFactor = barotrope.pressure_from_enthalpy(enthalpyValue); + } else { + const double enthalpyVariationValue = elementEnthalpyVariation * enthalpyShape; + + pressureFactor = + barotrope.pressure_derivative_from_enthalpy(enthalpyValue) * enthalpyVariationValue; + } + + displacementElement.CalcDShape(integrationPoint, displacementDShapeReference); /* * Row i of DShape is grad_reference(N_i). Multiplication @@ -411,17 +426,45 @@ namespace mean_field::operators::kernels { * grad_physical(N_i) * = grad_reference(N_i) J^{-1}. */ - mfem::Mult( - displacementDShapeReference, - mappingContext.quadrature.J_inv, displacementDShapePhysical - ); + mfem::Mult(displacementDShapeReference, mappingContext.quadrature.J_inv, displacementDShapePhysical); - const double weightedPressure = - pressureValue * mappingContext.quadrature.weight; + std::optional mappingVariation; + + if (pressureForceAction == PressureForceAction::displacement) { + mappingVariation.emplace(); + + const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation( + mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext, + workspace, *mappingVariation + ); + + MFEM_VERIFY( + variationStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping variation failed in the " + "pressure-force kernel. Element: " + << elementId << ", attribute: " << transformation->Attribute << ", quadrature point: " + << quadratureIndex << ", status: " << static_cast(variationStatus) + ); + + /* + * Differentiating + * + * grad_x(N_i) = grad_reference(N_i) J^{-1} + * + * at the frozen base geometry gives the physical + * test-gradient variation used by the geometric + * pressure block. + */ + mfem::Mult( + displacementDShapeReference, mappingVariation->inverse_element_jacobian_variation, + displacementDShapePhysicalVariation + ); + } + + const double weightedPressureFactor = pressureFactor * mappingContext.quadrature.weight; MFEM_VERIFY( - std::isfinite(pressureValue) && - std::isfinite(weightedPressure), + std::isfinite(pressureFactor) && std::isfinite(weightedPressureFactor), "The pressure-force kernel encountered a non-finite " "quadrature value." ); @@ -436,29 +479,96 @@ namespace mean_field::operators::kernels { * R_(i,c) * = -integral P partial_c N_i dV. */ - for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; - ++scalarDof) { - for (int component = 0; component < dimension; - ++component) { + for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) { + for (int component = 0; component < dimension; ++component) { const int vectorDof = vector_dof_index( - displacementOrdering, scalarDof, component, - scalarDisplacementDofCount, dimension + displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension ); - elementResidual(vectorDof) -= - weightedPressure * - displacementDShapePhysical(scalarDof, component); + if (pressureForceAction == PressureForceAction::displacement) { + /* + * Differentiate the complete discrete factor + * + * grad_x(N_i) dV_x. + * + * The enthalpy DOFs, and therefore P(h), are + * frozen in this Jacobian column. + */ + const double gradientWeightVariation = + mappingContext.quadrature.weight * + displacementDShapePhysicalVariation(scalarDof, component) + + mappingVariation->weight_variation * displacementDShapePhysical(scalarDof, component); + + const double contribution = pressureFactor * gradientWeightVariation; + + MFEM_VERIFY( + std::isfinite(gradientWeightVariation) && std::isfinite(contribution), + "The pressure-force geometry action " + "encountered a non-finite contribution." + ); + + elementAction(vectorDof) -= contribution; + } else { + elementAction(vectorDof) -= + weightedPressureFactor * displacementDShapePhysical(scalarDof, component); + } } } } if (displacementDofTransformation != nullptr) { - displacementDofTransformation->TransformDual(elementResidual); + displacementDofTransformation->TransformDual(elementAction); } - localResidual.AddElementVector(displacementDofs, elementResidual); + localAction.AddElementVector(displacementDofs, elementAction); } - local_to_true(*f.displacementFes, localResidual, residualTrue); + local_to_true(*f.displacementFes, localAction, actionTrue); } -} // namespace mean_field::operators::kernels \ No newline at end of file +} // namespace + +namespace mean_field::operators::kernels { + void apply_pressure_force_residual( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const mfem::Vector &enthalpyTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &residualTrue + ) { + apply_pressure_force_action( + f, domainMapper, barotrope, PressureForceAction::residual, enthalpyTrue, nullptr, nullptr, displacementTrue, + residualTrue + ); + } + + void apply_pressure_force_enthalpy_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const mfem::Vector &baseEnthalpyTrue, + const mfem::Vector &enthalpyVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_pressure_force_action( + f, domainMapper, barotrope, PressureForceAction::enthalpy, baseEnthalpyTrue, &enthalpyVariationTrue, + nullptr, displacementTrue, actionTrue + ); + } + + void apply_pressure_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const mfem::Vector &baseEnthalpyTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_pressure_force_action( + f, domainMapper, barotrope, PressureForceAction::displacement, baseEnthalpyTrue, nullptr, + &displacementVariationTrue, displacementTrue, actionTrue + ); + } +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp b/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp new file mode 100644 index 0000000..f21aff1 --- /dev/null +++ b/libmeanfield/impl/operators/kernels/rotation_displacement_force_kernels.cpp @@ -0,0 +1,590 @@ +module; + +#include +#include +#include + +#include + +module mean_field; + +import :operators.kernels.rotational_displacement_force; + +namespace { + enum class RotationalDisplacementForceAction { residual, density, displacement, complete }; + + void true_to_local( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &trueVector, + mfem::Vector &localVector + ) { + MFEM_VERIFY( + trueVector.Size() == finiteElementSpace.GetTrueVSize(), + "The rotational-displacement-force true vector has the wrong " + "size." + ); + + localVector.SetSize(finiteElementSpace.GetVSize()); + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->Mult(trueVector, localVector); + } else { + localVector = trueVector; + } + } + + void local_to_true( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &localVector, + mfem::Vector &trueVector + ) { + MFEM_VERIFY( + localVector.Size() == finiteElementSpace.GetVSize(), + "The rotational-displacement-force local vector has the wrong " + "size." + ); + + trueVector.SetSize(finiteElementSpace.GetTrueVSize()); + trueVector = 0.0; + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->MultTranspose(localVector, trueVector); + } else { + trueVector = localVector; + } + } + + [[nodiscard]] int vector_dof_index( + const mfem::Ordering::Type ordering, + const int scalarDof, + const int component, + const int scalarDofCount, + const int dimension + ) { + if (ordering == mfem::Ordering::byNODES) { + return scalarDof + component * scalarDofCount; + } + + if (ordering == mfem::Ordering::byVDIM) { + return scalarDof * dimension + component; + } + + MFEM_ABORT( + "The rotational-displacement-force test space uses an " + "unsupported ordering." + ); + + return -1; + } + + [[nodiscard]] const mfem::IntegrationRule &get_rotation_force_rule( + const mean_field::fem::FEM &f, + const mfem::FiniteElement &densityElement, + const mfem::FiniteElement &displacementElement, + const mfem::ElementTransformation &transformation + ) { + using DisplacementField = mean_field::field::Field; + + MFEM_VERIFY( + densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder, + "The rotational-displacement-force density element does not " + "match the registered density field." + ); + + MFEM_VERIFY( + displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder, + "The rotational-displacement-force test element does not match " + "the registered displacement field." + ); + + /* + * grad(Psi_rotation) is linear in physical position, so it adds one + * dynamic polynomial-order contribution. + */ + const mean_field::quadrature::Query query = + DisplacementField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array{1}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); + + const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType()); + + MFEM_VERIFY( + rule.integration_rule != nullptr, "The quadrature policy did not return a rotational-" + "displacement-force integration rule." + ); + + return *rule.integration_rule; + } + + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + void validate_common_inputs( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &displacementTrue + ) { + MFEM_VERIFY(f.mesh != nullptr, "The rotational-displacement-force kernel requires a mesh."); + + MFEM_VERIFY( + f.mesh->Dimension() == 3, "The rotational-displacement-force kernel requires a " + "three-dimensional mesh." + ); + + MFEM_VERIFY( + f.densityFes != nullptr, "The rotational-displacement-force kernel requires the density " + "finite-element space." + ); + + MFEM_VERIFY( + f.displacementFes != nullptr, "The rotational-displacement-force kernel requires the " + "displacement finite-element space." + ); + + MFEM_VERIFY( + f.compactificationFes != nullptr && f.compactificationCoordinate != nullptr, + "The rotational-displacement-force kernel requires the " + "compactification coordinate." + ); + + MFEM_VERIFY( + f.quadratureFactory != nullptr, "The rotational-displacement-force kernel requires the " + "quadrature-rule factory." + ); + + MFEM_VERIFY( + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), + "The rotational-displacement-force displacement vector has the " + "wrong size." + ); + + MFEM_VERIFY( + domainMapper.GetDimension() == f.mesh->Dimension(), + "The rotational-displacement-force mapper dimension does not " + "match the mesh dimension." + ); + + MFEM_VERIFY( + f.displacementFes->GetVDim() == f.mesh->Dimension(), + "The rotational-displacement-force displacement dimension does " + "not match the mesh dimension." + ); + + validate_finite_vector( + displacementTrue, "The rotational-displacement-force displacement contains a " + "non-finite value." + ); + } + + void validate_density( + const mean_field::fem::FEM &f, + const mfem::Vector &density, + const char *message + ) { + MFEM_VERIFY(density.Size() == f.densityFes->GetTrueVSize(), message); + validate_finite_vector(density, message); + } + + void apply_rotational_displacement_force_action( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const mean_field::physics::RigidRotation &rotation, + const RotationalDisplacementForceAction requestedAction, + const mfem::Vector *baseDensityTrue, + const mfem::Vector *densityVariationTrue, + const mfem::Vector *displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + validate_common_inputs(f, domainMapper, displacementTrue); + + const bool needsBaseDensity = requestedAction == RotationalDisplacementForceAction::residual || + requestedAction == RotationalDisplacementForceAction::displacement || + requestedAction == RotationalDisplacementForceAction::complete; + + const bool needsDensityVariation = requestedAction == RotationalDisplacementForceAction::density || + requestedAction == RotationalDisplacementForceAction::complete; + + const bool needsDisplacementVariation = requestedAction == RotationalDisplacementForceAction::displacement || + requestedAction == RotationalDisplacementForceAction::complete; + + if (needsBaseDensity) { + MFEM_VERIFY( + baseDensityTrue != nullptr, "The rotational-displacement-force action requires a base " + "density." + ); + + validate_density(f, *baseDensityTrue, "The rotational-displacement-force base density is invalid."); + } + + if (needsDensityVariation) { + MFEM_VERIFY( + densityVariationTrue != nullptr, "The rotational-displacement-force action requires a " + "density variation." + ); + + validate_density( + f, *densityVariationTrue, + "The rotational-displacement-force density variation is " + "invalid." + ); + } + + if (needsDisplacementVariation) { + MFEM_VERIFY( + displacementVariationTrue != nullptr && + displacementVariationTrue->Size() == f.displacementFes->GetTrueVSize(), + "The rotational-displacement-force displacement variation " + "is invalid." + ); + + validate_finite_vector( + *displacementVariationTrue, "The rotational-displacement-force displacement variation " + "contains a non-finite value." + ); + } + + mfem::Vector baseDensityLocal; + mfem::Vector densityVariationLocal; + mfem::Vector displacementLocal; + mfem::Vector displacementVariationLocal; + + if (needsBaseDensity) { + true_to_local(*f.densityFes, *baseDensityTrue, baseDensityLocal); + } + + if (needsDensityVariation) { + true_to_local(*f.densityFes, *densityVariationTrue, densityVariationLocal); + } + + true_to_local(*f.displacementFes, displacementTrue, displacementLocal); + + if (needsDisplacementVariation) { + true_to_local(*f.displacementFes, *displacementVariationTrue, displacementVariationLocal); + } + + mfem::Vector localAction(f.displacementFes->GetVSize()); + localAction = 0.0; + + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); + + mfem::Array densityDofs; + mfem::Array displacementDofs; + mfem::Array compactificationDofs; + + mfem::Vector elementBaseDensity; + mfem::Vector elementDensityVariation; + mfem::Vector elementDisplacement; + mfem::Vector elementDisplacementVariation; + mfem::Vector elementCompactification; + mfem::Vector elementAction; + + mfem::Vector densityShape; + mfem::Vector displacementShape; + mfem::Vector potentialGradient; + mfem::Vector potentialGradientVariation; + mfem::Vector centrifugalAcceleration; + mfem::Vector centrifugalAccelerationVariation; + mfem::Vector weightedForce; + + mean_field::mapping::VolumeMappingContext mappingContext; + mean_field::mapping::VolumeMappingVariation mappingVariation; + + const int dimension = f.mesh->Dimension(); + const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); + + const mfem::Ordering::Type displacementOrdering = f.displacementFes->GetOrdering(); + + for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); + + MFEM_VERIFY( + transformation != nullptr, "The rotational-displacement-force kernel received a null " + "element transformation." + ); + + if (transformation->Attribute == vacuumAttribute) { + continue; + } + + const mfem::FiniteElement &densityElement = *f.densityFes->GetFE(elementId); + + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); + + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); + + mfem::DofTransformation *densityDofTransformation = f.densityFes->GetElementDofs(elementId, densityDofs); + + mfem::DofTransformation *displacementDofTransformation = + f.displacementFes->GetElementVDofs(elementId, displacementDofs); + + mfem::DofTransformation *compactificationDofTransformation = + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); + + if (needsBaseDensity) { + baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity); + } + + if (needsDensityVariation) { + densityVariationLocal.GetSubVector(densityDofs, elementDensityVariation); + } + + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); + + if (needsDisplacementVariation) { + displacementVariationLocal.GetSubVector(displacementDofs, elementDisplacementVariation); + } + + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); + + if (densityDofTransformation != nullptr) { + if (needsBaseDensity) { + densityDofTransformation->InvTransformPrimal(elementBaseDensity); + } + + if (needsDensityVariation) { + densityDofTransformation->InvTransformPrimal(elementDensityVariation); + } + } + + if (displacementDofTransformation != nullptr) { + displacementDofTransformation->InvTransformPrimal(elementDisplacement); + + if (needsDisplacementVariation) { + displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); + } + } + + if (compactificationDofTransformation != nullptr) { + compactificationDofTransformation->InvTransformPrimal(elementCompactification); + } + + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); + + const mean_field::mapping::ElementCompactificationData compactificationData( + compactificationElement, elementCompactification + ); + + const mean_field::mapping::ElementMappingData mappingData{ + .displacement = displacementData, .compactification = compactificationData + }; + + std::optional displacementVariationData; + + if (needsDisplacementVariation) { + displacementVariationData.emplace( + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacementElement, elementDisplacementVariation + ) + ); + } + + const int scalarDisplacementDofCount = displacementElement.GetDof(); + + MFEM_VERIFY( + displacementDofs.Size() == scalarDisplacementDofCount * dimension, + "The rotational-displacement-force element displacement " + "vector has the wrong size." + ); + + densityShape.SetSize(densityElement.GetDof()); + displacementShape.SetSize(scalarDisplacementDofCount); + potentialGradient.SetSize(dimension); + potentialGradientVariation.SetSize(dimension); + centrifugalAcceleration.SetSize(dimension); + centrifugalAccelerationVariation.SetSize(dimension); + weightedForce.SetSize(dimension); + + elementAction.SetSize(displacementDofs.Size()); + elementAction = 0.0; + + const mfem::IntegrationRule &integrationRule = + get_rotation_force_rule(f, densityElement, displacementElement, *transformation); + + for (int quadratureIndex = 0; quadratureIndex < integrationRule.GetNPoints(); ++quadratureIndex) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadratureIndex); + + transformation->SetIntPoint(&integrationPoint); + + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); + + MFEM_VERIFY( + mappingStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping failed in the rotational-" + "displacement-force kernel. Element: " + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadratureIndex << ", status: " << static_cast(mappingStatus) + ); + + if (needsDisplacementVariation) { + const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation( + mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext, + workspace, mappingVariation + ); + + MFEM_VERIFY( + variationStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping variation failed in the " + "rotational-displacement-force kernel. Element: " + << elementId << ", attribute: " << transformation->Attribute << ", quadrature point: " + << quadratureIndex << ", status: " << static_cast(variationStatus) + ); + } + + densityElement.CalcShape(integrationPoint, densityShape); + + displacementElement.CalcShape(integrationPoint, displacementShape); + + double baseDensityValue = 0.0; + double densityVariationValue = 0.0; + + if (needsBaseDensity) { + baseDensityValue = elementBaseDensity * densityShape; + } + + if (needsDensityVariation) { + densityVariationValue = elementDensityVariation * densityShape; + } + + rotation.potential_gradient(mappingContext.mapping.physical_position, potentialGradient); + + centrifugalAcceleration = potentialGradient; + centrifugalAcceleration *= -1.0; + + if (needsDisplacementVariation) { + rotation.potential_gradient_directional_derivative( + mappingVariation.mapping.physical_position_variation, potentialGradientVariation + ); + + centrifugalAccelerationVariation = potentialGradientVariation; + + centrifugalAccelerationVariation *= -1.0; + } else { + centrifugalAccelerationVariation = 0.0; + } + + weightedForce = 0.0; + + if (requestedAction == RotationalDisplacementForceAction::residual) { + weightedForce.Add(baseDensityValue * mappingContext.quadrature.weight, centrifugalAcceleration); + } else { + if (needsDensityVariation) { + weightedForce.Add( + densityVariationValue * mappingContext.quadrature.weight, centrifugalAcceleration + ); + } + + if (needsDisplacementVariation) { + weightedForce.Add( + baseDensityValue * mappingContext.quadrature.weight, centrifugalAccelerationVariation + ); + + weightedForce.Add( + baseDensityValue * mappingVariation.weight_variation, centrifugalAcceleration + ); + } + } + + for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) { + for (int component = 0; component < dimension; ++component) { + const int vectorDof = vector_dof_index( + displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension + ); + + const double contribution = displacementShape(scalarDof) * weightedForce(component); + + MFEM_VERIFY( + std::isfinite(contribution), "The rotational-displacement-force kernel " + "encountered a non-finite contribution." + ); + + elementAction(vectorDof) += contribution; + } + } + } + + if (displacementDofTransformation != nullptr) { + displacementDofTransformation->TransformDual(elementAction); + } + + localAction.AddElementVector(displacementDofs, elementAction); + } + + local_to_true(*f.displacementFes, localAction, actionTrue); + } +} // namespace + +namespace mean_field::operators::kernels { + void apply_rotational_displacement_force_residual( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &densityTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &residualTrue + ) { + apply_rotational_displacement_force_action( + f, domainMapper, rotation, RotationalDisplacementForceAction::residual, &densityTrue, nullptr, nullptr, + displacementTrue, residualTrue + ); + } + + void apply_rotational_displacement_force_density_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_rotational_displacement_force_action( + f, domainMapper, rotation, RotationalDisplacementForceAction::density, nullptr, &densityVariationTrue, + nullptr, displacementTrue, actionTrue + ); + } + + void apply_rotational_displacement_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_rotational_displacement_force_action( + f, domainMapper, rotation, RotationalDisplacementForceAction::displacement, &baseDensityTrue, nullptr, + &displacementVariationTrue, displacementTrue, actionTrue + ); + } + + void apply_rotational_displacement_force_complete_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ) { + apply_rotational_displacement_force_action( + f, domainMapper, rotation, RotationalDisplacementForceAction::complete, &baseDensityTrue, + &densityVariationTrue, &displacementVariationTrue, displacementTrue, actionTrue + ); + } +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/impl/operators/prepared_barotropic_closure.cpp b/libmeanfield/impl/operators/prepared_barotropic_closure.cpp index f26d9b8..5da77ca 100644 --- a/libmeanfield/impl/operators/prepared_barotropic_closure.cpp +++ b/libmeanfield/impl/operators/prepared_barotropic_closure.cpp @@ -5,38 +5,54 @@ module; #include #include #include +#include module mean_field; import :operators.prepared_barotropic_closure; +import :operators.kernels.barotropic_closure; +import :field.registry; +import :utils.domain; namespace { - int get_density_size(const mean_field::fem::FEM &f) { - MFEM_VERIFY( - f.densityFes != nullptr, - "PreparedBarotropicClosureOperator requires the " - "density finite-element space." - ); + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + using ClosureDomain = mean_field::field::FieldDomainT; - return f.densityFes->GetTrueVSize(); + void verify_required_spaces(const mean_field::fem::FEM &f) { + MFEM_VERIFY(f.mesh != nullptr, "PreparedBarotropicClosureOperator requires a mesh."); + MFEM_VERIFY( + f.densityFes != nullptr, "PreparedBarotropicClosureOperator requires the density finite-element space." + ); + MFEM_VERIFY( + f.enthalpyFes != nullptr, "PreparedBarotropicClosureOperator requires the enthalpy finite-element space." + ); + MFEM_VERIFY( + f.displacementFes != nullptr, + "PreparedBarotropicClosureOperator requires the displacement finite-element space." + ); + MFEM_VERIFY( + f.compactificationFes != nullptr, + "PreparedBarotropicClosureOperator requires the compactification finite-element space." + ); + MFEM_VERIFY( + f.compactificationCoordinate != nullptr, + "PreparedBarotropicClosureOperator requires the compactification coordinate." + ); + MFEM_VERIFY( + f.quadratureFactory != nullptr, "PreparedBarotropicClosureOperator requires the quadrature factory." + ); } - int get_enthalpy_size(const mean_field::fem::FEM &f) { - MFEM_VERIFY( - f.enthalpyFes != nullptr, - "PreparedBarotropicClosureOperator requires the " - "enthalpy finite-element space." - ); - - return f.enthalpyFes->GetTrueVSize(); + [[nodiscard]] bool element_is_in_closure_support(const int attribute) { + return DomainSchema::template attribute_belongs_to(attribute); } void validate_finite_vector( const mfem::Vector &vector, const char *message ) { - for (int i = 0; i < vector.Size(); ++i) { - MFEM_VERIFY(std::isfinite(vector(i)), message); + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); } } @@ -45,15 +61,11 @@ namespace { const mfem::Vector &trueVector, mfem::Vector &localVector ) { - MFEM_VERIFY( - trueVector.Size() == finiteElementSpace.GetTrueVSize(), - "True vector has the wrong size." - ); + MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size."); localVector.SetSize(finiteElementSpace.GetVSize()); - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(trueVector, localVector); @@ -67,16 +79,12 @@ namespace { const mfem::Vector &localVector, mfem::Vector &trueVector ) { - MFEM_VERIFY( - localVector.Size() == finiteElementSpace.GetVSize(), - "Local vector has the wrong size." - ); + MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "Local vector has the wrong size."); trueVector.SetSize(finiteElementSpace.GetTrueVSize()); - trueVector = 0.0; + trueVector = 0.0; - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(localVector, trueVector); @@ -85,65 +93,57 @@ namespace { } } - int get_eos_extra_order( - const mean_field::physics::PolytropicBarotrope &barotrope - ) { - const double extraOrder = - (barotrope.polytropic_index() - 1.0) * - static_cast( - mean_field::field::Enthalpy::Scalar::familyOrder - ); + [[nodiscard]] int get_eos_extra_order(const mean_field::eos::Polytrope &equationOfState) { + const double extraOrder = (equationOfState.polytropic_index() - 1.0) * + static_cast(mean_field::field::Enthalpy::Scalar::familyOrder); MFEM_VERIFY( std::isfinite(extraOrder) && extraOrder >= 0.0 && - extraOrder <= - static_cast(std::numeric_limits::max()), + extraOrder <= static_cast(std::numeric_limits::max()), "The EOS effective polynomial order is invalid." ); return static_cast(std::ceil(extraOrder)); } - const mfem::IntegrationRule &get_eos_rule( + [[nodiscard]] const mfem::IntegrationRule &get_eos_rule( const mean_field::fem::FEM &f, - const mean_field::physics::PolytropicBarotrope &barotrope, + const mean_field::eos::Polytrope &equationOfState, const mfem::FiniteElement &densityElement, const mfem::FiniteElement &enthalpyElement, const mfem::ElementTransformation &transformation ) { - using EnthalpyField = - mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; MFEM_VERIFY( - densityElement.GetOrder() == - mean_field::field::Density::Scalar::familyOrder, - "The prepared EOS test element does not match " - "the registered density field." + densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder, + "The prepared EOS test element does not match the registered density field." ); MFEM_VERIFY( - enthalpyElement.GetOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder, - "The prepared EOS trial element does not match " - "the registered enthalpy field." + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, + "The prepared EOS trial element does not match the registered enthalpy field." ); - const mean_field::quadrature::Query query = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EosClosureSource>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), - std::array{get_eos_extra_order(barotrope)}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general - ); + /* + * The quadrature Query still carries the legacy DOMAINS metadata. + * Element support itself is no longer selected through that enum; + * support is determined above through Density::Support + DomainSchema. + * The Query metadata can be migrated independently with the quadrature + * subsystem without changing this operator's algebra. + */ + const mean_field::quadrature::Query query = + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), + std::array{get_eos_extra_order(equationOfState)}, mean_field::utils::DOMAINS::STELLAR, + mean_field::quadrature::MappingKind::general + ); - const auto resolution = - f.quadratureFactory->get(query, transformation.GetGeometryType()); + const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType()); MFEM_VERIFY( resolution.integration_rule != nullptr, - "The quadrature policy did not return a prepared " - "EOS-closure integration rule." + "The quadrature policy did not return a prepared EOS-closure integration rule." ); return *resolution.integration_rule; @@ -151,91 +151,127 @@ namespace { } // namespace namespace mean_field::operators { + struct PreparedBarotropicClosureOperator::ConstructionData final { + field::FieldDofMap densityMap; + field::FieldDofMap enthalpyMap; + field::FieldDofMap displacementMap; + + explicit ConstructionData(const fem::FEM &f) + : densityMap( + field::make_field_dof_map< + field::Density, + DomainSchema>(*f.densityFes) + ), + enthalpyMap( + field::make_field_dof_map< + field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ), + displacementMap( + field::make_field_dof_map< + field::Displacement, + DomainSchema>(*f.displacementFes) + ) { + } + }; + + PreparedBarotropicClosureOperator::ConstructionData + PreparedBarotropicClosureOperator::MakeConstructionData(const fem::FEM &f) { + verify_required_spaces(f); + return ConstructionData(f); + } + PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope + const eos::Polytrope &equationOfState + ) + : PreparedBarotropicClosureOperator( + f, + domainMapper, + equationOfState, + MakeConstructionData(f) + ) { + } + + PreparedBarotropicClosureOperator::PreparedBarotropicClosureOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + ConstructionData constructionData ) : mfem::Operator( - f.densityFes->GetTrueVSize(), - f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize() + - f.displacementFes->GetTrueVSize() + constructionData.densityMap.reduced_size(), + constructionData.densityMap.reduced_size() + constructionData.enthalpyMap.reduced_size() + + constructionData.displacementMap.reduced_size() ), m_fem(f), m_domainMapper(domainMapper), - m_barotrope(barotrope), - m_densitySize(f.densityFes->GetTrueVSize()), - m_enthalpySize(f.enthalpyFes->GetTrueVSize()) { + m_equationOfState(equationOfState), + m_densityMap(std::move(constructionData.densityMap)), + m_enthalpyMap(std::move(constructionData.enthalpyMap)), + m_displacementMap(std::move(constructionData.displacementMap)), + m_context( + f, + domainMapper, + m_densityMap, + m_enthalpyMap, + m_displacementMap + ) { MFEM_VERIFY( - m_fem.densityFes != nullptr, - "PreparedBarotropicClosureOperator requires " - "a density finite-element space." + m_densityMap.full_size() == m_fem.densityFes->GetTrueVSize(), + "The density FieldDofMap does not match the density finite-element space." + ); + MFEM_VERIFY( + m_enthalpyMap.full_size() == m_fem.enthalpyFes->GetTrueVSize(), + "The enthalpy FieldDofMap does not match the enthalpy finite-element space." + ); + MFEM_VERIFY( + m_displacementMap.full_size() == m_fem.displacementFes->GetTrueVSize(), + "The displacement FieldDofMap does not match the displacement finite-element space." ); - MFEM_VERIFY( - m_fem.enthalpyFes != nullptr, - "PreparedBarotropicClosureOperator requires " - "an enthalpy finite-element space." - ); + m_baseDensityTrue.SetSize(m_densityMap.full_size()); + m_baseEnthalpyTrue.SetSize(m_enthalpyMap.full_size()); + m_baseDisplacementTrue.SetSize(m_displacementMap.full_size()); - MFEM_VERIFY( - m_fem.displacementFes != nullptr, - "PreparedBarotropicClosureOperator requires " - "a displacement finite-element space." - ); + m_densityVariationTrue.SetSize(m_densityMap.full_size()); + m_enthalpyVariationTrue.SetSize(m_enthalpyMap.full_size()); + m_displacementVariationTrue.SetSize(m_displacementMap.full_size()); + m_fullThermodynamicAction.SetSize(m_densityMap.full_size()); + m_fullDisplacementAction.SetSize(m_densityMap.full_size()); + m_fullResidual.SetSize(m_densityMap.full_size()); + + m_baseDensityTrue = 0.0; + m_baseEnthalpyTrue = 0.0; + m_baseDisplacementTrue = 0.0; + m_densityVariationTrue = 0.0; + m_enthalpyVariationTrue = 0.0; + m_displacementVariationTrue = 0.0; + m_fullThermodynamicAction = 0.0; + m_fullDisplacementAction = 0.0; + m_fullResidual = 0.0; } - void PreparedBarotropicClosureOperator::Prepare( - const mfem::Vector &baseDensityTrue, - const mfem::Vector &baseEnthalpyTrue, - const mfem::Vector &displacementTrue + PreparedBarotropicClosureReport PreparedBarotropicClosureOperator::Prepare( + const context::barotropic::BarotropicClosureStateView &state, + const context::barotropic::BarotropicClosureDependencies &dependencies ) { - MFEM_VERIFY( - baseDensityTrue.Size() == m_densitySize, - "PreparedBarotropicClosureOperator received a " - "base-density vector with the wrong size." - ); + PreparedBarotropicClosureReport report; + report.contextReport = m_context.Prepare(state, dependencies); - MFEM_VERIFY( - baseEnthalpyTrue.Size() == m_enthalpySize, - "PreparedBarotropicClosureOperator received a " - "base-enthalpy vector with the wrong size." - ); + if (!report.contextReport.DidAnyWork() && m_isPrepared) { + return report; + } - MFEM_VERIFY( - displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(), - "PreparedBarotropicClosureOperator received a " - "displacement vector with the wrong size." - ); - MFEM_VERIFY( - baseDensityTrue.Size() == m_fem.densityFes->GetTrueVSize(), - "The base density true vector has the wrong size." - ); - - MFEM_VERIFY( - baseEnthalpyTrue.Size() == m_fem.enthalpyFes->GetTrueVSize(), - "The base enthalpy true vector has the wrong size." - ); - - MFEM_VERIFY( - displacementTrue.Size() == m_fem.displacementFes->GetTrueVSize(), - "The base displacement true vector has the wrong size." - ); - - validate_finite_vector( - baseDensityTrue, "PreparedBarotropicClosureOperator received a " - "non-finite base-density value." - ); - - validate_finite_vector( - baseEnthalpyTrue, "PreparedBarotropicClosureOperator received a " - "non-finite base-enthalpy value." - ); - - validate_finite_vector( - displacementTrue, "PreparedBarotropicClosureOperator received a " - "non-finite displacement value." - ); + /* + * Canonical solver -> MFEM expansion. Unsupported density and + * enthalpy DOFs are zero. Displacement is currently an identity map, + * but it is deliberately routed through the same abstraction. + */ + m_densityMap.scatter(m_context.GetBaseDensity(), m_baseDensityTrue); + m_enthalpyMap.scatter(m_context.GetBaseEnthalpy(), m_baseEnthalpyTrue); + m_displacementMap.scatter(m_context.GetDisplacement(), m_baseDisplacementTrue); m_isPrepared = false; m_elements.clear(); @@ -245,17 +281,11 @@ namespace mean_field::operators { mfem::Vector baseEnthalpyLocal; mfem::Vector displacementLocal; - true_to_local(*m_fem.densityFes, baseDensityTrue, baseDensityLocal); + true_to_local(*m_fem.densityFes, m_baseDensityTrue, baseDensityLocal); + true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, baseEnthalpyLocal); + true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal); - true_to_local(*m_fem.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal); - - true_to_local( - *m_fem.displacementFes, displacementTrue, displacementLocal - ); - - mapping::DomainMapperStateless::Workspace workspace( - m_fem.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mfem::Array displacementDofs; mfem::Array compactificationDofs; @@ -268,222 +298,134 @@ namespace mean_field::operators { mfem::Vector densityShape; mfem::Vector enthalpyShape; - const int vacuumAttribute = m_domainMapper.GetVacuumElementAttribute(); - for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - m_fem.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "PreparedBarotropicClosureOperator received " - "a null element transformation." + transformation != nullptr, "PreparedBarotropicClosureOperator received a null element transformation." ); - if (transformation->Attribute == vacuumAttribute) { + if (!element_is_in_closure_support(transformation->Attribute)) { continue; } m_elements.emplace_back(); - ElementPAData &data = m_elements.back(); + ElementPAData &data = m_elements.back(); - data.densityDofTransformation = - m_fem.densityFes->GetElementDofs(elementId, data.densityDofs); - - data.enthalpyDofTransformation = - m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs); + data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs); + data.enthalpyDofTransformation = m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs); mfem::DofTransformation *displacementDofTransformation = - m_fem.displacementFes->GetElementVDofs( - elementId, displacementDofs - ); - + m_fem.displacementFes->GetElementVDofs(elementId, displacementDofs); mfem::DofTransformation *compactificationDofTransformation = - m_fem.compactificationFes->GetElementDofs( - elementId, compactificationDofs - ); + m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs); baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity); - - baseEnthalpyLocal.GetSubVector( - data.enthalpyDofs, elementBaseEnthalpy - ); - - displacementLocal.GetSubVector( - displacementDofs, elementDisplacement - ); - - m_fem.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + baseEnthalpyLocal.GetSubVector(data.enthalpyDofs, elementBaseEnthalpy); + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); + m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (data.densityDofTransformation != nullptr) { - data.densityDofTransformation->InvTransformPrimal( - elementBaseDensity - ); + data.densityDofTransformation->InvTransformPrimal(elementBaseDensity); } - if (data.enthalpyDofTransformation != nullptr) { - data.enthalpyDofTransformation->InvTransformPrimal( - elementBaseEnthalpy - ); + data.enthalpyDofTransformation->InvTransformPrimal(elementBaseEnthalpy); } - if (displacementDofTransformation != nullptr) { - displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + displacementDofTransformation->InvTransformPrimal(elementDisplacement); } - if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } - const mfem::FiniteElement &densityElement = - *m_fem.densityFes->GetFE(elementId); - - const mfem::FiniteElement &enthalpyElement = - *m_fem.enthalpyFes->GetFE(elementId); - - const mfem::FiniteElement &displacementElement = - *m_fem.displacementFes->GetFE(elementId); - - const mfem::FiniteElement &compactificationElement = - *m_fem.compactificationFes->GetFE(elementId); + const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *m_fem.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId); + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId); const mapping::ElementDisplacementData displacementData = - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); const mapping::ElementCompactificationData compactificationData( compactificationElement, elementCompactification ); const mapping::ElementMappingData mappingData{ - .displacement = displacementData, - .compactification = compactificationData + .displacement = displacementData, .compactification = compactificationData }; - const mfem::IntegrationRule &integrationRule = get_eos_rule( - m_fem, m_barotrope, densityElement, enthalpyElement, - *transformation - ); + const mfem::IntegrationRule &integrationRule = + get_eos_rule(m_fem, m_equationOfState, densityElement, enthalpyElement, *transformation); const int quadraturePointCount = integrationRule.GetNPoints(); - const int densityDofCount = densityElement.GetDof(); - const int enthalpyDofCount = enthalpyElement.GetDof(); data.densityBasis.SetSize(quadraturePointCount, densityDofCount); - data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount); - data.weightedResidual.SetSize(quadraturePointCount); - data.quadratureWeights.SetSize(quadraturePointCount); - data.weightedEnthalpyDerivative.SetSize(quadraturePointCount); densityShape.SetSize(densityDofCount); - enthalpyShape.SetSize(enthalpyDofCount); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(quadraturePoint); transformation->SetIntPoint(&integrationPoint); mapping::VolumeMappingContext mappingContext; - const mapping::MappingStatus mappingStatus = - m_domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( mappingStatus == mapping::MappingStatus::valid, - "Stateless mapping failed while preparing " - "the barotropic closure operator. Element: " - << elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadraturePoint - << ", status: " << static_cast(mappingStatus) + "Stateless mapping failed while preparing the barotropic closure operator. Element: " + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) ); densityElement.CalcShape(integrationPoint, densityShape); - enthalpyElement.CalcShape(integrationPoint, enthalpyShape); - for (int densityDof = 0; densityDof < densityDofCount; - ++densityDof) { - data.densityBasis(quadraturePoint, densityDof) = - densityShape(densityDof); + for (int densityDof = 0; densityDof < densityDofCount; ++densityDof) { + data.densityBasis(quadraturePoint, densityDof) = densityShape(densityDof); + } + for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) { + data.enthalpyBasis(quadraturePoint, enthalpyDof) = enthalpyShape(enthalpyDof); } - for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; - ++enthalpyDof) { - data.enthalpyBasis(quadraturePoint, enthalpyDof) = - enthalpyShape(enthalpyDof); - } - - const double density = elementBaseDensity * densityShape; - - const double enthalpy = elementBaseEnthalpy * enthalpyShape; - - const double quadratureWeight = - mappingContext.quadrature.weight; - - const double eosDensity = - m_barotrope.density_from_enthalpy(enthalpy); - - const double enthalpyDerivative = - m_barotrope.density_derivative_from_enthalpy(enthalpy); + const double density = elementBaseDensity * densityShape; + const double enthalpy = elementBaseEnthalpy * enthalpyShape; + const double quadratureWeight = mappingContext.quadrature.weight; + const double eosDensity = m_equationOfState.density_from_enthalpy(enthalpy); + const double enthalpyDerivative = m_equationOfState.density_derivative_from_enthalpy(enthalpy); MFEM_VERIFY( - std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && - std::isfinite(eosDensity) && + std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) && std::isfinite(enthalpyDerivative), - "PreparedBarotropicClosureOperator " - "encountered invalid quadrature data." + "PreparedBarotropicClosureOperator encountered invalid quadrature data." ); - data.quadratureWeights(quadraturePoint) = quadratureWeight; - - data.weightedResidual(quadraturePoint) = - quadratureWeight * (density - eosDensity); - - data.weightedEnthalpyDerivative(quadraturePoint) = - quadratureWeight * enthalpyDerivative; + data.quadratureWeights(quadraturePoint) = quadratureWeight; + data.weightedResidual(quadraturePoint) = quadratureWeight * (density - eosDensity); + data.weightedEnthalpyDerivative(quadraturePoint) = quadratureWeight * enthalpyDerivative; } } - MFEM_VERIFY( - !m_elements.empty(), "PreparedBarotropicClosureOperator found no " - "stellar elements." - ); + MFEM_VERIFY(!m_elements.empty(), "PreparedBarotropicClosureOperator found no elements in Density::Support."); - m_baseDensityTrue = baseDensityTrue; - m_baseEnthalpyTrue = baseEnthalpyTrue; - m_baseDisplacementTrue = displacementTrue; - - m_isPrepared = true; + m_isPrepared = true; ++m_preparationCount; + report.preparedElementData = true; + return report; } - void PreparedBarotropicClosureOperator::BuildResidual( - mfem::Vector &residual - ) const { - MFEM_VERIFY( - m_isPrepared, "PreparedBarotropicClosureOperator must be " - "prepared before BuildResidual is called." - ); + void PreparedBarotropicClosureOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); mfem::Vector localResidual(m_fem.densityFes->GetVSize()); localResidual = 0.0; @@ -492,10 +434,7 @@ namespace mean_field::operators { for (const ElementPAData &data : m_elements) { elementResidual.SetSize(data.densityDofs.Size()); - - data.densityBasis.MultTranspose( - data.weightedResidual, elementResidual - ); + data.densityBasis.MultTranspose(data.weightedResidual, elementResidual); if (data.densityDofTransformation != nullptr) { data.densityDofTransformation->TransformDual(elementResidual); @@ -504,53 +443,56 @@ namespace mean_field::operators { localResidual.AddElementVector(data.densityDofs, elementResidual); } - local_to_true(*m_fem.densityFes, localResidual, residual); + local_to_true(*m_fem.densityFes, localResidual, m_fullResidual); + residual.SetSize(m_densityMap.reduced_size()); + m_densityMap.gather(m_fullResidual, residual); } void PreparedBarotropicClosureOperator::Mult( - const mfem::Vector &densityVariationTrue, - const mfem::Vector &enthalpyVariationTrue, - const mfem::Vector &displacementVariationTrue, + const mfem::Vector &densityVariation, + const mfem::Vector &enthalpyVariation, + const mfem::Vector &displacementVariation, mfem::Vector &action ) const { VerifyPrepared(); MFEM_VERIFY( - densityVariationTrue.Size() == m_densitySize, - "The density-variation true vector has " - "the wrong size." + densityVariation.Size() == m_densityMap.reduced_size(), + "The supported density-variation vector has the wrong size." ); - MFEM_VERIFY( - enthalpyVariationTrue.Size() == m_enthalpySize, - "The enthalpy-variation true vector has " - "the wrong size." + enthalpyVariation.Size() == m_enthalpyMap.reduced_size(), + "The supported enthalpy-variation vector has the wrong size." ); - MFEM_VERIFY( - displacementVariationTrue.Size() == - m_fem.displacementFes->GetTrueVSize(), - "The displacement-variation true vector has " - "the wrong size." + displacementVariation.Size() == m_displacementMap.reduced_size(), + "The supported displacement-variation vector has the wrong size." ); - Mult(densityVariationTrue, enthalpyVariationTrue, action); + validate_finite_vector(densityVariation, "The density variation contains a non-finite value."); + validate_finite_vector(enthalpyVariation, "The enthalpy variation contains a non-finite value."); + validate_finite_vector(displacementVariation, "The displacement variation contains a non-finite value."); - mfem::Vector displacementAction; + m_densityMap.scatter(densityVariation, m_densityVariationTrue); + m_enthalpyMap.scatter(enthalpyVariation, m_enthalpyVariationTrue); + m_displacementMap.scatter(displacementVariation, m_displacementVariationTrue); + + ApplyThermodynamicActionFull(m_densityVariationTrue, m_enthalpyVariationTrue, m_fullThermodynamicAction); kernels::apply_barotropic_closure_displacement_action( - m_fem, m_domainMapper, m_barotrope, m_baseDensityTrue, - m_baseEnthalpyTrue, m_baseDisplacementTrue, - displacementVariationTrue, displacementAction + m_fem, m_domainMapper, m_equationOfState, m_baseDensityTrue, m_baseEnthalpyTrue, m_baseDisplacementTrue, + m_displacementVariationTrue, m_fullDisplacementAction ); MFEM_VERIFY( - displacementAction.Size() == m_densitySize, - "The barotropic-closure displacement action " - "returned a vector with the wrong size." + m_fullThermodynamicAction.Size() == m_densityMap.full_size() && + m_fullDisplacementAction.Size() == m_densityMap.full_size(), + "A full barotropic-closure Jacobian action has an incompatible density-space size." ); - action += displacementAction; + m_fullThermodynamicAction += m_fullDisplacementAction; + action.SetSize(m_densityMap.reduced_size()); + m_densityMap.gather(m_fullThermodynamicAction, action); } void PreparedBarotropicClosureOperator::Mult( @@ -559,70 +501,40 @@ namespace mean_field::operators { ) const { VerifyPrepared(); - const int displacementSize = m_fem.displacementFes->GetTrueVSize(); - - const int combinedSize = - m_densitySize + m_enthalpySize + displacementSize; - MFEM_VERIFY( - combinedVariation.Size() == combinedSize, - "The combined barotropic-closure variation " - "vector has the wrong size. Expected " - << combinedSize << " entries but received " - << combinedVariation.Size() << "." + combinedVariation.Size() == Width(), "The packed supported barotropic-closure variation has the wrong size." ); - mfem::real_t *combinedData = - const_cast(combinedVariation.HostRead()); + mfem::real_t *combinedData = const_cast(combinedVariation.HostRead()); - const mfem::Vector densityVariationTrue(combinedData, m_densitySize); + const int densitySize = m_densityMap.reduced_size(); + const int enthalpySize = m_enthalpyMap.reduced_size(); + const int displacementSize = m_displacementMap.reduced_size(); - const mfem::Vector enthalpyVariationTrue( - combinedData + m_densitySize, m_enthalpySize - ); + const mfem::Vector densityVariation(combinedData, densitySize); + const mfem::Vector enthalpyVariation(combinedData + densitySize, enthalpySize); + const mfem::Vector displacementVariation(combinedData + densitySize + enthalpySize, displacementSize); - const mfem::Vector displacementVariationTrue( - combinedData + m_densitySize + m_enthalpySize, displacementSize - ); - - Mult( - densityVariationTrue, enthalpyVariationTrue, - displacementVariationTrue, action - ); + Mult(densityVariation, enthalpyVariation, displacementVariation, action); } - void PreparedBarotropicClosureOperator::Mult( + void PreparedBarotropicClosureOperator::ApplyThermodynamicActionFull( const mfem::Vector &densityVariationTrue, const mfem::Vector &enthalpyVariationTrue, - mfem::Vector &action + mfem::Vector &actionTrue ) const { MFEM_VERIFY( - m_isPrepared, "PreparedBarotropicClosureOperator must be " - "prepared before Mult is called." + densityVariationTrue.Size() == m_densityMap.full_size(), "The full density variation has the wrong size." ); - MFEM_VERIFY( - densityVariationTrue.Size() == m_densitySize, - "PreparedBarotropicClosureOperator received a " - "density variation with the wrong size." - ); - - MFEM_VERIFY( - enthalpyVariationTrue.Size() == m_enthalpySize, - "PreparedBarotropicClosureOperator received an " - "enthalpy variation with the wrong size." + enthalpyVariationTrue.Size() == m_enthalpyMap.full_size(), "The full enthalpy variation has the wrong size." ); mfem::Vector densityVariationLocal; mfem::Vector enthalpyVariationLocal; - true_to_local( - *m_fem.densityFes, densityVariationTrue, densityVariationLocal - ); - - true_to_local( - *m_fem.enthalpyFes, enthalpyVariationTrue, enthalpyVariationLocal - ); + true_to_local(*m_fem.densityFes, densityVariationTrue, densityVariationLocal); + true_to_local(*m_fem.enthalpyFes, enthalpyVariationTrue, enthalpyVariationLocal); mfem::Vector localAction(m_fem.densityFes->GetVSize()); localAction = 0.0; @@ -635,51 +547,30 @@ namespace mean_field::operators { mfem::Vector elementAction; for (const ElementPAData &data : m_elements) { - densityVariationLocal.GetSubVector( - data.densityDofs, elementDensityVariation - ); - - enthalpyVariationLocal.GetSubVector( - data.enthalpyDofs, elementEnthalpyVariation - ); + densityVariationLocal.GetSubVector(data.densityDofs, elementDensityVariation); + enthalpyVariationLocal.GetSubVector(data.enthalpyDofs, elementEnthalpyVariation); if (data.densityDofTransformation != nullptr) { - data.densityDofTransformation->InvTransformPrimal( - elementDensityVariation - ); + data.densityDofTransformation->InvTransformPrimal(elementDensityVariation); } - if (data.enthalpyDofTransformation != nullptr) { - data.enthalpyDofTransformation->InvTransformPrimal( - elementEnthalpyVariation - ); + data.enthalpyDofTransformation->InvTransformPrimal(elementEnthalpyVariation); } quadratureDensityVariation.SetSize(data.quadratureWeights.Size()); - quadratureEnthalpyVariation.SetSize(data.quadratureWeights.Size()); - quadratureAction.SetSize(data.quadratureWeights.Size()); - data.densityBasis.Mult( - elementDensityVariation, quadratureDensityVariation - ); + data.densityBasis.Mult(elementDensityVariation, quadratureDensityVariation); + data.enthalpyBasis.Mult(elementEnthalpyVariation, quadratureEnthalpyVariation); - data.enthalpyBasis.Mult( - elementEnthalpyVariation, quadratureEnthalpyVariation - ); - - for (int quadraturePoint = 0; - quadraturePoint < quadratureAction.Size(); ++quadraturePoint) { + for (int quadraturePoint = 0; quadraturePoint < quadratureAction.Size(); ++quadraturePoint) { quadratureAction(quadraturePoint) = - data.quadratureWeights(quadraturePoint) * - quadratureDensityVariation(quadraturePoint) - - data.weightedEnthalpyDerivative(quadraturePoint) * - quadratureEnthalpyVariation(quadraturePoint); + data.quadratureWeights(quadraturePoint) * quadratureDensityVariation(quadraturePoint) - + data.weightedEnthalpyDerivative(quadraturePoint) * quadratureEnthalpyVariation(quadraturePoint); } elementAction.SetSize(data.densityDofs.Size()); - data.densityBasis.MultTranspose(quadratureAction, elementAction); if (data.densityDofTransformation != nullptr) { @@ -689,30 +580,42 @@ namespace mean_field::operators { localAction.AddElementVector(data.densityDofs, elementAction); } - local_to_true(*m_fem.densityFes, localAction, action); + local_to_true(*m_fem.densityFes, localAction, actionTrue); } bool PreparedBarotropicClosureOperator::IsPrepared() const noexcept { - return m_isPrepared; + return m_isPrepared && m_context.IsPrepared(); } - std::uint64_t - PreparedBarotropicClosureOperator::GetPreparationCount() const noexcept { + std::uint64_t PreparedBarotropicClosureOperator::GetPreparationCount() const noexcept { return m_preparationCount; } int PreparedBarotropicClosureOperator::GetDensitySize() const noexcept { - return m_densitySize; + return m_densityMap.reduced_size(); } int PreparedBarotropicClosureOperator::GetEnthalpySize() const noexcept { - return m_enthalpySize; + return m_enthalpyMap.reduced_size(); + } + + int PreparedBarotropicClosureOperator::GetDisplacementSize() const noexcept { + return m_displacementMap.reduced_size(); + } + + const context::barotropic::BarotropicClosureLinearizationContext & + PreparedBarotropicClosureOperator::GetContext() const noexcept { + return m_context; + } + + const context::barotropic::BarotropicClosurePreparationStatistics & + PreparedBarotropicClosureOperator::GetContextPreparationStatistics() const noexcept { + return m_context.GetPreparationStatistics(); } void PreparedBarotropicClosureOperator::VerifyPrepared() const { MFEM_VERIFY( - m_isPrepared, "PreparedBarotropicClosureOperator must be " - "prepared before this operation is called." + m_isPrepared, "PreparedBarotropicClosureOperator must be prepared before this operation is called." ); } -} // namespace mean_field::operators \ No newline at end of file +} // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/prepared_displacement_operator.cpp b/libmeanfield/impl/operators/prepared_displacement_operator.cpp new file mode 100644 index 0000000..da7dcbb --- /dev/null +++ b/libmeanfield/impl/operators/prepared_displacement_operator.cpp @@ -0,0 +1,554 @@ +module; + +#include + +module mean_field; + +import :operators.prepared_displacement_residual; + +namespace { + using Dependencies = mean_field::operators::DisplacementResidualDependencies; + + [[nodiscard]] mean_field::operators::context::pressure_force::PressureForceDependencies + make_pressure_dependencies(const Dependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}, + .displacement = { + .identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision + } + }; + } + + [[nodiscard]] mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies + make_rotational_dependencies(const Dependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision}, + .displacement = + {.identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision}, + .rotation = {.identity = dependencies.rotation.identity, .revision = dependencies.rotation.revision} + }; + } + + void validate_shared_gravity_revisions( + const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext, + const Dependencies &dependencies + ) { + MFEM_VERIFY( + gravityContext.IsPrepared(), "PreparedDisplacementResidualOperator requires the shared " + "gravity linearization context to be prepared first." + ); + + const mean_field::operators::context::gravity_field::GravityFieldRevisions &gravityRevisions = + gravityContext.GetRevisions(); + + MFEM_VERIFY( + gravityRevisions.discretization.value == dependencies.discretization.revision && + gravityRevisions.density.value == dependencies.density.revision && + gravityRevisions.displacement.value == dependencies.displacement.revision && + gravityRevisions.gravity_gradient.value == dependencies.gravityGradient.revision, + "PreparedDisplacementResidualOperator received dependency " + "revisions that do not match the shared gravity context." + ); + } + + void validate_shared_identity_transition( + const mean_field::operators::DisplacementResidualDependencyStamp &prepared, + const mean_field::operators::DisplacementResidualDependencyStamp &requested, + const char *message + ) { + MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message); + } + + void add_compatible( + mfem::Vector &destination, + const mfem::Vector &source, + const char *message + ) { + MFEM_VERIFY(destination.Size() == source.Size(), message); + destination += source; + } +} // namespace + +namespace mean_field::operators { + PreparedDisplacementResidualOperator::PreparedDisplacementResidualOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ) + : m_fem(f), + m_domainMapper(domainMapper), + m_gravityContext(gravityContext), + m_pressureOperator( + f, + domainMapper, + barotrope + ), + m_gravityOperator( + f, + domainMapper, + gravityContext + ), + m_rotationalOperator( + f, + domainMapper + ) { + MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedDisplacementResidualOperator requires a mesh."); + + MFEM_VERIFY( + m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr && m_fem.gravityFluxFes != nullptr && + m_fem.enthalpyFes != nullptr, + "PreparedDisplacementResidualOperator requires density, " + "displacement, gravity-gradient, and enthalpy finite-element " + "spaces." + ); + + MFEM_VERIFY( + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), + "PreparedDisplacementResidualOperator received a mapper with " + "the wrong dimension." + ); + } + + PreparedDisplacementResidualReport PreparedDisplacementResidualOperator::Prepare( + const DisplacementResidualStateView &state, + const DisplacementResidualDependencies &dependencies, + const physics::RigidRotation &rotation + ) { + validate_shared_gravity_revisions(m_gravityContext, dependencies); + + if (m_isPrepared) { + /* + * GravityFieldLinearizationContext currently tracks revisions + * but not semantic identities. Require an identity replacement + * to be accompanied by a visible revision change so it cannot + * silently reuse the old shared density, geometry, or flux. + */ + validate_shared_identity_transition( + m_preparedDependencies.discretization, dependencies.discretization, + "A new displacement-residual discretization identity must " + "also change the shared gravity revision." + ); + + validate_shared_identity_transition( + m_preparedDependencies.density, dependencies.density, + "A new displacement-residual density identity must also " + "change the shared gravity revision." + ); + + validate_shared_identity_transition( + m_preparedDependencies.displacement, dependencies.displacement, + "A new displacement-residual displacement identity must " + "also change the shared gravity revision." + ); + + validate_shared_identity_transition( + m_preparedDependencies.gravityGradient, dependencies.gravityGradient, + "A new displacement-residual gravity-gradient identity " + "must also change the shared gravity revision." + ); + } + + const mfem::Vector &density = m_gravityContext.GetDensity(); + const mfem::Vector &displacement = m_gravityContext.GetGeometryContext().GetDisplacement(); + + m_isPrepared = false; + + PreparedDisplacementResidualReport report; + + report.pressure = m_pressureOperator.Prepare( + {.enthalpy = state.enthalpy, .displacement = displacement}, make_pressure_dependencies(dependencies) + ); + + report.gravity = m_gravityOperator.Prepare(); + + report.rotation = m_rotationalOperator.Prepare( + {.density = density, .displacement = displacement}, make_rotational_dependencies(dependencies), rotation + ); + + if (report.DidAnyChildWork() || m_cachedResidual.Size() != m_fem.displacementFes->GetTrueVSize()) { + AssembleResidual(); + report.assembledResidual = true; + } + + MFEM_VERIFY( + m_cachedResidual.Size() == m_fem.displacementFes->GetTrueVSize(), + "PreparedDisplacementResidualOperator produced a cached " + "residual with the wrong size." + ); + + m_preparedDependencies = dependencies; + m_isPrepared = true; + return report; + } + + void PreparedDisplacementResidualOperator::AssembleResidual() { + mfem::Vector pressureResidual; + mfem::Vector gravityResidual; + mfem::Vector rotationalResidual; + + m_pressureOperator.BuildResidual(pressureResidual); + m_gravityOperator.BuildResidual(gravityResidual); + m_rotationalOperator.BuildResidual(rotationalResidual); + + m_cachedResidual = pressureResidual; + + add_compatible( + m_cachedResidual, gravityResidual, + "Cannot combine pressure and gravity displacement residuals " + "with different sizes." + ); + + add_compatible( + m_cachedResidual, rotationalResidual, + "Cannot combine mechanical displacement residuals with " + "different sizes." + ); + + ++m_residualPreparationCount; + } + + void PreparedDisplacementResidualOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + residual = m_cachedResidual; + ++m_residualApplicationCount; + } + + void PreparedDisplacementResidualOperator::ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + mfem::Vector rotationalAction; + + m_gravityOperator.ApplyDensityJacobianAction(densityVariation, action); + + m_rotationalOperator.ApplyDensityJacobianAction(densityVariation, rotationalAction); + + add_compatible( + action, rotationalAction, + "Cannot combine gravity and rotation density-column actions " + "with different sizes." + ); + + ++m_actionStatistics.densityApplications; + } + + void PreparedDisplacementResidualOperator::ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + mfem::Vector gravityAction; + mfem::Vector rotationalAction; + + m_pressureOperator.ApplyDisplacementJacobianAction(displacementVariation, action); + + m_gravityOperator.ApplyDisplacementJacobianAction(displacementVariation, gravityAction); + + m_rotationalOperator.ApplyDisplacementJacobianAction(displacementVariation, rotationalAction); + + add_compatible( + action, gravityAction, + "Cannot combine pressure and gravity displacement-column " + "actions with different sizes." + ); + + add_compatible( + action, rotationalAction, + "Cannot combine mechanical displacement-column actions with " + "different sizes." + ); + + ++m_actionStatistics.displacementApplications; + } + + void PreparedDisplacementResidualOperator::ApplyGravityGradientJacobianAction( + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + m_gravityOperator.ApplyGravityGradientJacobianAction(gravityGradientVariation, action); + + ++m_actionStatistics.gravityGradientApplications; + } + + void PreparedDisplacementResidualOperator::ApplyEnthalpyJacobianAction( + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + m_pressureOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, action); + + ++m_actionStatistics.enthalpyApplications; + } + + void PreparedDisplacementResidualOperator::ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + const mfem::Vector &gravityGradientVariation, + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + mfem::Vector gravityAction; + mfem::Vector rotationalAction; + + m_pressureOperator.ApplyCompleteJacobianAction(enthalpyVariation, displacementVariation, action); + + m_gravityOperator.ApplyCompleteJacobianAction( + densityVariation, displacementVariation, gravityGradientVariation, gravityAction + ); + + m_rotationalOperator.ApplyCompleteJacobianAction(densityVariation, displacementVariation, rotationalAction); + + add_compatible( + action, gravityAction, + "Cannot combine pressure and gravity complete Jacobian " + "actions with different sizes." + ); + + add_compatible( + action, rotationalAction, + "Cannot combine mechanical complete Jacobian actions with " + "different sizes." + ); + + ++m_actionStatistics.densityApplications; + ++m_actionStatistics.displacementApplications; + ++m_actionStatistics.gravityGradientApplications; + ++m_actionStatistics.enthalpyApplications; + ++m_actionStatistics.completeApplications; + } + + bool PreparedDisplacementResidualOperator::IsPrepared() const noexcept { + if (!m_isPrepared || !m_pressureOperator.IsPrepared() || !m_gravityOperator.IsPrepared() || + !m_rotationalOperator.IsPrepared() || !m_gravityContext.IsPrepared()) { + return false; + } + + const context::gravity_field::GravityFieldRevisions &gravityRevisions = m_gravityContext.GetRevisions(); + + return gravityRevisions.discretization.value == m_preparedDependencies.discretization.revision && + gravityRevisions.density.value == m_preparedDependencies.density.revision && + gravityRevisions.displacement.value == m_preparedDependencies.displacement.revision && + gravityRevisions.gravity_gradient.value == m_preparedDependencies.gravityGradient.revision; + } + + std::uint64_t PreparedDisplacementResidualOperator::GetResidualPreparationCount() const noexcept { + return m_residualPreparationCount; + } + + std::uint64_t PreparedDisplacementResidualOperator::GetResidualApplicationCount() const noexcept { + return m_residualApplicationCount; + } + + const PreparedDisplacementResidualActionStatistics & + PreparedDisplacementResidualOperator::GetActionStatistics() const noexcept { + return m_actionStatistics; + } + + const PreparedPressureForceOperator &PreparedDisplacementResidualOperator::GetPressureOperator() const noexcept { + return m_pressureOperator; + } + + const PreparedGravityDisplacementForceOperator & + PreparedDisplacementResidualOperator::GetGravityOperator() const noexcept { + return m_gravityOperator; + } + + const PreparedRotationalDisplacementForceOperator & + PreparedDisplacementResidualOperator::GetRotationalOperator() const noexcept { + return m_rotationalOperator; + } + + const fem::FEM &PreparedDisplacementResidualOperator::GetFEM() const noexcept { + return m_fem; + } + + const context::gravity_field::GravityFieldLinearizationContext & + PreparedDisplacementResidualOperator::GetGravityContext() const noexcept { + return m_gravityContext; + } + + void PreparedDisplacementResidualOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedDisplacementResidualOperator must be prepared for " + "the current shared gravity-context revisions before residual " + "or Jacobian application." + ); + } + + PreparedDisplacementResidualJacobianOperator::PreparedDisplacementResidualJacobianOperator( + const DisplacementResidualLayout &layout, + const PreparedDisplacementResidualOperator &preparedOperator + ) + : mfem::Operator( + layout.residual_offsets().Last(), + layout.value_offsets().Last() + ), + m_layout(layout), + m_preparedOperator(preparedOperator) { + const fem::FEM &f = m_preparedOperator.GetFEM(); + + MFEM_VERIFY( + f.densityFes != nullptr && f.displacementFes != nullptr && f.gravityFluxFes != nullptr && + f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr, + "Prepared displacement-residual MFEM adapter requires every " + "finite-element space in the barotropic equilibrium layout." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); + + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + + constexpr auto barotropicConstantValue = + utils::blocks::get_value_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + constexpr auto gravityGradientResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); + + constexpr auto densityResidual = + utils::blocks::get_residual_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto enthalpyResidual = + utils::blocks::get_residual_block(utils::blocks::enthalpy_field.specific_term); + + constexpr auto massResidual = + utils::blocks::get_residual_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + MFEM_VERIFY( + m_layout.size(densityValue) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementValue) == f.displacementFes->GetTrueVSize() && + m_layout.size(gravityGradientValue) == f.gravityFluxFes->GetTrueVSize() && + m_layout.size(gravityPotentialValue) == f.gravityPotentialFes->GetTrueVSize() && + m_layout.size(barotropicConstantValue) == 1, + "Prepared displacement-residual MFEM adapter received " + "incompatible barotropic value-block sizes." + ); + + MFEM_VERIFY( + m_layout.size(enthalpyValue) == m_preparedOperator.GetPressureOperator().GetEnthalpySize(), + "Prepared displacement-residual MFEM adapter received an " + "incompatible enthalpy value block." + ); + + MFEM_VERIFY( + m_layout.size(gravityGradientResidual) == f.gravityFluxFes->GetTrueVSize() && + m_layout.size(gravityPotentialResidual) == f.gravityPotentialFes->GetTrueVSize() && + m_layout.size(densityResidual) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementResidual) == f.displacementFes->GetTrueVSize() && + m_layout.size(enthalpyResidual) == f.enthalpyFes->GetTrueVSize() && m_layout.size(massResidual) == 1, + "Prepared displacement-residual MFEM adapter received " + "incompatible barotropic residual-block sizes." + ); + + MFEM_VERIFY( + Height() == m_layout.residual_offsets().Last() && Width() == m_layout.value_offsets().Last(), + "Prepared displacement-residual MFEM adapter has inconsistent " + "operator dimensions." + ); + } + + void PreparedDisplacementResidualJacobianOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + MFEM_VERIFY( + m_preparedOperator.IsPrepared(), "Prepared displacement-residual MFEM adapter requires a " + "prepared row operator." + ); + + MFEM_VERIFY( + direction.Size() == Width(), "Prepared displacement-residual MFEM adapter received a " + "direction with the wrong size." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + const mfem::Vector densityVariation( + const_cast(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue) + ); + + const mfem::Vector displacementVariation( + const_cast(direction.GetData()) + m_layout.offset(displacementValue), + m_layout.size(displacementValue) + ); + + const mfem::Vector gravityGradientVariation( + const_cast(direction.GetData()) + m_layout.offset(gravityGradientValue), + m_layout.size(gravityGradientValue) + ); + + const mfem::Vector enthalpyVariation( + const_cast(direction.GetData()) + m_layout.offset(enthalpyValue), + m_layout.size(enthalpyValue) + ); + + mfem::Vector displacementAction; + + m_preparedOperator.ApplyCompleteJacobianAction( + densityVariation, displacementVariation, gravityGradientVariation, enthalpyVariation, displacementAction + ); + + MFEM_VERIFY( + displacementAction.Size() == m_layout.size(displacementResidual), + "Prepared displacement-residual MFEM adapter produced an " + "action with the wrong size." + ); + + action.SetSize(Height()); + action = 0.0; + + const int residualOffset = m_layout.offset(displacementResidual); + + for (int entry = 0; entry < displacementAction.Size(); ++entry) { + action(residualOffset + entry) = displacementAction(entry); + } + } + + const DisplacementResidualLayout &PreparedDisplacementResidualJacobianOperator::GetLayout() const noexcept { + return m_layout; + } +} // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp b/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp new file mode 100644 index 0000000..34a3f00 --- /dev/null +++ b/libmeanfield/impl/operators/prepared_gravity_displacement_force.cpp @@ -0,0 +1,290 @@ +module; + +#include + +module mean_field; + +import :operators.kernels.gravity_displacement_force; +import :operators.prepared_gravity_displacement_force; + +namespace { + [[nodiscard]] bool relevant_revisions_match( + const mean_field::operators::context::gravity_field::GravityFieldRevisions &left, + const mean_field::operators::context::gravity_field::GravityFieldRevisions &right + ) noexcept { + return left.discretization == right.discretization && left.displacement == right.displacement && + left.density == right.density && left.gravity_gradient == right.gravity_gradient; + } +} // namespace + +namespace mean_field::operators { + PreparedGravityDisplacementForceOperator::PreparedGravityDisplacementForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ) + : m_fem(f), + m_domainMapper(domainMapper), + m_gravityContext(gravityContext) { + MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedGravityDisplacementForceOperator requires a mesh."); + + MFEM_VERIFY( + m_fem.densityFes != nullptr && m_fem.gravityFluxFes != nullptr && m_fem.displacementFes != nullptr, + "PreparedGravityDisplacementForceOperator requires density, " + "gravity-gradient, and displacement finite-element spaces." + ); + + MFEM_VERIFY( + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), + "PreparedGravityDisplacementForceOperator received a mapper " + "with the wrong dimension." + ); + } + + PreparedGravityDisplacementForceReport PreparedGravityDisplacementForceOperator::Prepare() { + MFEM_VERIFY( + m_gravityContext.IsPrepared(), "PreparedGravityDisplacementForceOperator requires the shared " + "gravity linearization context to be prepared first." + ); + + const context::gravity_field::GravityFieldRevisions &requestedRevisions = m_gravityContext.GetRevisions(); + + if (m_isPrepared && relevant_revisions_match(requestedRevisions, m_preparedRevisions)) { + return {}; + } + + kernels::apply_gravity_displacement_force_residual( + m_fem, m_domainMapper, m_gravityContext.GetDensity(), m_gravityContext.GetGravityGradient(), + m_gravityContext.GetGeometryContext().GetDisplacement(), m_cachedResidual + ); + + m_preparedRevisions = requestedRevisions; + ++m_residualPreparationCount; + m_isPrepared = true; + + return {.preparedResidual = true}; + } + + void PreparedGravityDisplacementForceOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + residual = m_cachedResidual; + ++m_residualApplicationCount; + } + + void PreparedGravityDisplacementForceOperator::ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_gravity_displacement_force_density_action( + m_fem, m_domainMapper, densityVariation, m_gravityContext.GetGravityGradient(), + m_gravityContext.GetGeometryContext().GetDisplacement(), action + ); + + ++m_densityJacobianStatistics.applications; + } + + void PreparedGravityDisplacementForceOperator::ApplyGravityGradientJacobianAction( + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_gravity_displacement_force_gradient_action( + m_fem, m_domainMapper, m_gravityContext.GetDensity(), gravityGradientVariation, + m_gravityContext.GetGeometryContext().GetDisplacement(), action + ); + + ++m_gravityGradientJacobianStatistics.applications; + } + + void PreparedGravityDisplacementForceOperator::ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_gravity_displacement_force_displacement_action( + m_fem, m_domainMapper, m_gravityContext.GetDensity(), m_gravityContext.GetGravityGradient(), + displacementVariation, m_gravityContext.GetGeometryContext().GetDisplacement(), action + ); + + ++m_displacementJacobianStatistics.applications; + } + + void PreparedGravityDisplacementForceOperator::ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_gravity_displacement_force_complete_action( + m_fem, m_domainMapper, m_gravityContext.GetDensity(), densityVariation, + m_gravityContext.GetGravityGradient(), gravityGradientVariation, displacementVariation, + m_gravityContext.GetGeometryContext().GetDisplacement(), action + ); + + ++m_densityJacobianStatistics.applications; + ++m_gravityGradientJacobianStatistics.applications; + ++m_displacementJacobianStatistics.applications; + ++m_completeJacobianStatistics.applications; + } + + bool PreparedGravityDisplacementForceOperator::IsPrepared() const noexcept { + if (!m_isPrepared || !m_gravityContext.IsPrepared()) { + return false; + } + + return relevant_revisions_match(m_gravityContext.GetRevisions(), m_preparedRevisions); + } + + std::uint64_t PreparedGravityDisplacementForceOperator::GetResidualPreparationCount() const noexcept { + return m_residualPreparationCount; + } + + std::uint64_t PreparedGravityDisplacementForceOperator::GetResidualApplicationCount() const noexcept { + return m_residualApplicationCount; + } + + const PreparedGravityDisplacementForceColumnStatistics & + PreparedGravityDisplacementForceOperator::GetDensityJacobianStatistics() const noexcept { + return m_densityJacobianStatistics; + } + + const PreparedGravityDisplacementForceColumnStatistics & + PreparedGravityDisplacementForceOperator::GetGravityGradientJacobianStatistics() const noexcept { + return m_gravityGradientJacobianStatistics; + } + + const PreparedGravityDisplacementForceColumnStatistics & + PreparedGravityDisplacementForceOperator::GetDisplacementJacobianStatistics() const noexcept { + return m_displacementJacobianStatistics; + } + + const PreparedGravityDisplacementForceCompleteStatistics & + PreparedGravityDisplacementForceOperator::GetCompleteJacobianStatistics() const noexcept { + return m_completeJacobianStatistics; + } + + const fem::FEM &PreparedGravityDisplacementForceOperator::GetFEM() const noexcept { + return m_fem; + } + + const context::gravity_field::GravityFieldLinearizationContext & + PreparedGravityDisplacementForceOperator::GetGravityContext() const noexcept { + return m_gravityContext; + } + + void PreparedGravityDisplacementForceOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedGravityDisplacementForceOperator must be prepared for " + "the current shared gravity-context revisions before residual or " + "Jacobian application." + ); + } + + PreparedGravityDisplacementForceJacobianOperator::PreparedGravityDisplacementForceJacobianOperator( + const GravityDisplacementForceLayout &layout, + const PreparedGravityDisplacementForceOperator &preparedOperator + ) + : mfem::Operator( + layout.residual_offsets().Last(), + layout.value_offsets().Last() + ), + m_layout(layout), + m_preparedOperator(preparedOperator) { + const fem::FEM &f = m_preparedOperator.GetFEM(); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + MFEM_VERIFY( + m_layout.size(densityValue) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementValue) == f.displacementFes->GetTrueVSize() && + m_layout.size(gravityGradientValue) == f.gravityFluxFes->GetTrueVSize() && + m_layout.size(displacementResidual) == f.displacementFes->GetTrueVSize(), + "Prepared gravity-displacement-force MFEM adapter received " + "incompatible coupled block sizes." + ); + } + + void PreparedGravityDisplacementForceJacobianOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + MFEM_VERIFY( + m_preparedOperator.IsPrepared(), "Prepared gravity-displacement-force MFEM adapter requires a " + "prepared operator." + ); + + MFEM_VERIFY( + direction.Size() == Width(), "Prepared gravity-displacement-force MFEM adapter received a " + "direction with the wrong size." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + const mfem::Vector densityVariation( + const_cast(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue) + ); + + const mfem::Vector displacementVariation( + const_cast(direction.GetData()) + m_layout.offset(displacementValue), + m_layout.size(displacementValue) + ); + + const mfem::Vector gravityGradientVariation( + const_cast(direction.GetData()) + m_layout.offset(gravityGradientValue), + m_layout.size(gravityGradientValue) + ); + + mfem::Vector displacementAction; + + m_preparedOperator.ApplyCompleteJacobianAction( + densityVariation, displacementVariation, gravityGradientVariation, displacementAction + ); + + MFEM_VERIFY( + displacementAction.Size() == m_layout.size(displacementResidual), + "Prepared gravity-displacement-force MFEM adapter produced a " + "displacement action with the wrong size." + ); + + action.SetSize(Height()); + action = 0.0; + + const int residualOffset = m_layout.offset(displacementResidual); + + for (int entry = 0; entry < displacementAction.Size(); ++entry) { + action(residualOffset + entry) = displacementAction(entry); + } + } + + const GravityDisplacementForceLayout &PreparedGravityDisplacementForceJacobianOperator::GetLayout() const noexcept { + return m_layout; + } +} // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/impl/operators/prepared_gravity_source.cpp b/libmeanfield/impl/operators/prepared_gravity_source.cpp index bd86c70..e70251e 100644 --- a/libmeanfield/impl/operators/prepared_gravity_source.cpp +++ b/libmeanfield/impl/operators/prepared_gravity_source.cpp @@ -11,19 +11,17 @@ import :operators.prepared_gravity_source; namespace { int get_operator_height(const mean_field::fem::FEM &f) { MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "PreparedMappedGravitySourceOperator requires the " - "gravity-potential " - "finite-element space." + f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the " + "gravity-potential " + "finite-element space." ); return f.gravityPotentialFes->GetTrueVSize(); } int get_operator_width(const mean_field::fem::FEM &f) { MFEM_VERIFY( - f.densityFes != nullptr, - "PreparedMappedGravitySourceOperator requires the density " - "finite-element space." + f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density " + "finite-element space." ); return f.densityFes->GetTrueVSize(); } @@ -35,8 +33,7 @@ namespace { ) { local_vector.SetSize(finite_element_space.GetVSize()); - const mfem::Operator *prolongation = - finite_element_space.GetProlongationMatrix(); + const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(true_vector, local_vector); @@ -50,16 +47,12 @@ namespace { const mfem::Vector &local_vector, mfem::Vector &true_vector ) { - MFEM_VERIFY( - local_vector.Size() == finite_element_space.GetVSize(), - "Local vector has the wrong size." - ); + MFEM_VERIFY(local_vector.Size() == finite_element_space.GetVSize(), "Local vector has the wrong size."); true_vector.SetSize(finite_element_space.GetTrueVSize()); - true_vector = 0.0; + true_vector = 0.0; - const mfem::Operator *prolongation = - finite_element_space.GetProlongationMatrix(); + const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(local_vector, true_vector); @@ -74,34 +67,27 @@ namespace { const mfem::FiniteElement &potential_element, const mfem::ElementTransformation &transformation ) { - using GravityField = - mean_field::field::Field; + using GravityField = mean_field::field::Field; MFEM_VERIFY( - density_element.GetOrder() == - mean_field::field::Density::Scalar::familyOrder, + density_element.GetOrder() == mean_field::field::Density::Scalar::familyOrder, "The prepared source trial element does not match the registered " "density field." ); MFEM_VERIFY( - potential_element.GetOrder() == - mean_field::field::Gravity::Potential::familyOrder, + potential_element.GetOrder() == mean_field::field::Gravity::Potential::familyOrder, "The prepared source test element does not match the registered " "gravity potential." ); - const mean_field::quadrature::Query query = GravityField::make_query< - mean_field::field::Gravity::Form::SourceProjection>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general - ); + const mean_field::quadrature::Query query = + GravityField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); - return *f.quadratureFactory - ->get(query, transformation.GetGeometryType()) - .integration_rule; + return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule; } - class FrozenMappedGravitySourceCoefficient final - : public mfem::Coefficient { + class FrozenMappedGravitySourceCoefficient final : public mfem::Coefficient { public: FrozenMappedGravitySourceCoefficient( const mean_field::fem::FEM &f, @@ -111,9 +97,7 @@ namespace { : m_fem(f), m_domain_mapper(domain_mapper), m_workspace(domain_mapper.GetDimension()) { - true_to_local( - *m_fem.displacementFes, displacement_true, m_displacement_local - ); + true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local); } double Eval( @@ -128,79 +112,55 @@ namespace { "Mapped gravity source coefficient received an invalid element " "ID." ); - if (transformation.Attribute == - m_domain_mapper.GetVacuumElementAttribute()) { + if (transformation.Attribute == m_domain_mapper.GetVacuumElementAttribute()) { return 0.0; } LoadElement(element_id); const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = *m_displacement_data, - .compactification = *m_compactification_data + .displacement = *m_displacement_data, .compactification = *m_compactification_data }; mean_field::mapping::VolumeMappingContext mapping_context; - const mean_field::mapping::MappingStatus status = - m_domain_mapper.EvaluateVolume( - mapping_data, transformation, integration_point, - m_workspace, mapping_context - ); + const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume( + mapping_data, transformation, integration_point, m_workspace, mapping_context + ); if (status != mean_field::mapping::MappingStatus::valid) { - const mfem::FiniteElement &displacement_element = - *m_fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *m_fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); mfem::Vector displacement_shape(displacement_element.GetDof()); - mfem::Vector compactification_shape( - compactification_element.GetDof() - ); + mfem::Vector compactification_shape(compactification_element.GetDof()); mfem::Vector reference_position(m_domain_mapper.GetDimension()); mfem::Vector displacement_value(m_domain_mapper.GetDimension()); - displacement_element.CalcShape( - integration_point, displacement_shape - ); - compactification_element.CalcShape( - integration_point, compactification_shape - ); + displacement_element.CalcShape(integration_point, displacement_shape); + compactification_element.CalcShape(integration_point, compactification_shape); transformation.Transform(integration_point, reference_position); - m_displacement_data->GetDofMatrix().MultTranspose( - displacement_shape, displacement_value - ); + m_displacement_data->GetDofMatrix().MultTranspose(displacement_shape, displacement_value); - const double compactification_coordinate = - m_compactification_data->GetDofs() * compactification_shape; + const double compactification_coordinate = m_compactification_data->GetDofs() * compactification_shape; MFEM_ABORT( "Stateless domain mapping failed while preparing the " "gravity " "source operator." - << "\nMapping status = " << static_cast(status) - << "\nElement ID = " << element_id + << "\nMapping status = " << static_cast(status) << "\nElement ID = " << element_id << "\nElement attribute = " << transformation.Attribute - << "\nIntegration-point index = " << integration_point.index - << "\nIntegration point = <" << integration_point.x << ", " - << integration_point.y << ", " << integration_point.z << ">" - << "\nReference position = <" << reference_position(0) - << ", " << reference_position(1) << ", " + << "\nIntegration-point index = " << integration_point.index << "\nIntegration point = <" + << integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">" + << "\nReference position = <" << reference_position(0) << ", " << reference_position(1) << ", " << reference_position(2) << ">" - << "\nReference radius = " << reference_position.Norml2() - << "\nDisplacement value = <" << displacement_value(0) - << ", " << displacement_value(1) << ", " - << displacement_value(2) << ">" - << "\nDisplacement magnitude = " - << displacement_value.Norml2() - << "\nCompactification coordinate = " - << compactification_coordinate - << "\nDisplacement ordering = " - << static_cast(m_fem.displacementFes->GetOrdering()) + << "\nReference radius = " << reference_position.Norml2() << "\nDisplacement value = <" + << displacement_value(0) << ", " << displacement_value(1) << ", " << displacement_value(2) << ">" + << "\nDisplacement magnitude = " << displacement_value.Norml2() + << "\nCompactification coordinate = " << compactification_coordinate + << "\nDisplacement ordering = " << static_cast(m_fem.displacementFes->GetOrdering()) ); } - const double mapping_determinant = - mapping_context.mapping.mapping_determinant; + const double mapping_determinant = mapping_context.mapping.mapping_determinant; MFEM_VERIFY( std::isfinite(mapping_determinant) && mapping_determinant > 0.0, "Prepared gravity source operator encountered a non-positive " @@ -208,8 +168,7 @@ namespace { "non-finite mapping determinant." ); - return 4.0 * std::numbers::pi * mean_field::utils::G * - mapping_determinant; + return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant; } private: @@ -218,48 +177,32 @@ namespace { return; } - const mfem::FiniteElement &displacement_element = - *m_fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *m_fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); mfem::DofTransformation *displacement_dof_transformation = - m_fem.displacementFes->GetElementVDofs( - element_id, m_displacement_dofs - ); + m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - m_fem.compactificationFes->GetElementDofs( - element_id, m_compactification_dofs - ); + m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs); - m_displacement_local.GetSubVector( - m_displacement_dofs, m_element_displacement - ); - m_fem.compactificationCoordinate->GetSubVector( - m_compactification_dofs, m_element_compactification - ); + m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement); + m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - m_element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(m_element_displacement); } if (compactification_dof_transformation != nullptr) { - compactification_dof_transformation->InvTransformPrimal( - m_element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(m_element_compactification); } - m_displacement_data = std::make_unique< - mean_field::mapping::ElementDisplacementData>( + m_displacement_data = std::make_unique( mean_field::mapping::ElementDisplacementDataFromElementVDofs( displacement_element, m_element_displacement ) ); - m_compactification_data = std::make_unique< - mean_field::mapping::ElementCompactificationData>( + m_compactification_data = std::make_unique( compactification_element, m_element_compactification ); @@ -277,10 +220,8 @@ namespace { mfem::Vector m_element_displacement; mfem::Vector m_element_compactification; - std::unique_ptr - m_displacement_data; - std::unique_ptr - m_compactification_data; + std::unique_ptr m_displacement_data; + std::unique_ptr m_compactification_data; mean_field::mapping::DomainMapperStateless::Workspace m_workspace; int m_cached_element_id{-1}; @@ -298,30 +239,23 @@ namespace mean_field::operators { ), m_fem(f), m_domain_mapper(domain_mapper) { + MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedGravitySourceOperator requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, - "PreparedMappedGravitySourceOperator requires a mesh." + f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density " + "finite-element space." ); MFEM_VERIFY( - f.densityFes != nullptr, - "PreparedMappedGravitySourceOperator requires the density " - "finite-element space." + f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the " + "gravity-potential " + "finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "PreparedMappedGravitySourceOperator requires the " - "gravity-potential " - "finite-element space." + f.displacementFes != nullptr, "PreparedMappedGravitySourceOperator requires " + "the displacement finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "PreparedMappedGravitySourceOperator requires " - "the displacement finite-element space." - ); - MFEM_VERIFY( - f.compactificationFes != nullptr, - "PreparedMappedGravitySourceOperator requires the compactification " - "finite-element space." + f.compactificationFes != nullptr, "PreparedMappedGravitySourceOperator requires the compactification " + "finite-element space." ); MFEM_VERIFY( f.compactificationCoordinate != nullptr, @@ -329,9 +263,8 @@ namespace mean_field::operators { "coordinate." ); MFEM_VERIFY( - f.quadratureFactory != nullptr, - "PreparedMappedGravitySourceOperator " - "requires the quadrature-rule factory." + f.quadratureFactory != nullptr, "PreparedMappedGravitySourceOperator " + "requires the quadrature-rule factory." ); MFEM_VERIFY( domain_mapper.GetDimension() == f.mesh->Dimension(), @@ -339,14 +272,10 @@ namespace mean_field::operators { "dimension." ); - utils::populate_element_mask( - f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker - ); + utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker); } - void PreparedMappedGravitySourceOperator::Prepare( - const mfem::Vector &displacement_true - ) { + void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement_true) { MFEM_VERIFY( displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(), "PreparedMappedGravitySourceOperator received a displacement " @@ -356,9 +285,8 @@ namespace mean_field::operators { for (int i = 0; i < displacement_true.Size(); ++i) { MFEM_VERIFY( - std::isfinite(displacement_true(i)), - "PreparedMappedGravitySourceOperator received a non-finite " - "displacement value." + std::isfinite(displacement_true(i)), "PreparedMappedGravitySourceOperator received a non-finite " + "displacement value." ); } @@ -366,44 +294,33 @@ namespace mean_field::operators { m_elements.clear(); m_elements.reserve(m_fem.mesh->GetNE()); - FrozenMappedGravitySourceCoefficient source_coefficient( - m_fem, m_domain_mapper, displacement_true - ); + FrozenMappedGravitySourceCoefficient source_coefficient(m_fem, m_domain_mapper, displacement_true); - for (int element_id = 0; element_id < m_fem.mesh->GetNE(); - ++element_id) { + for (int element_id = 0; element_id < m_fem.mesh->GetNE(); ++element_id) { const int attribute = m_fem.mesh->GetAttribute(element_id); - if (attribute <= 0 || attribute > m_stellar_marker.Size() || - m_stellar_marker[attribute - 1] == 0) { + if (attribute <= 0 || attribute > m_stellar_marker.Size() || m_stellar_marker[attribute - 1] == 0) { continue; } m_elements.emplace_back(); - ElementPAData &data = m_elements.back(); + ElementPAData &data = m_elements.back(); - data.element_id = element_id; + data.element_id = element_id; - data.density_dof_transformation = - m_fem.densityFes->GetElementDofs(element_id, data.density_dofs); + data.density_dof_transformation = m_fem.densityFes->GetElementDofs(element_id, data.density_dofs); data.potential_dof_transformation = - m_fem.gravityPotentialFes->GetElementDofs( - element_id, data.potential_dofs - ); + m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs); - const mfem::FiniteElement &density_element = - *m_fem.densityFes->GetFE(element_id); + const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id); - const mfem::FiniteElement &potential_element = - *m_fem.gravityPotentialFes->GetFE(element_id); + const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id); - mfem::ElementTransformation &transformation = - *m_fem.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id); - const mfem::IntegrationRule &integration_rule = get_source_rule( - m_fem, density_element, potential_element, transformation - ); + const mfem::IntegrationRule &integration_rule = + get_source_rule(m_fem, density_element, potential_element, transformation); const int quadrature_point_count = integration_rule.GetNPoints(); @@ -411,24 +328,17 @@ namespace mean_field::operators { const int potential_dof_count = potential_element.GetDof(); - data.density_basis.SetSize( - quadrature_point_count, density_dof_count - ); + data.density_basis.SetSize(quadrature_point_count, density_dof_count); - data.potential_basis.SetSize( - quadrature_point_count, potential_dof_count - ); + data.potential_basis.SetSize(quadrature_point_count, potential_dof_count); data.quadrature_data.SetSize(quadrature_point_count); mfem::Vector density_shape(density_dof_count); mfem::Vector potential_shape(potential_dof_count); - for (int quadrature_point = 0; - quadrature_point < quadrature_point_count; - ++quadrature_point) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(quadrature_point); + for (int quadrature_point = 0; quadrature_point < quadrature_point_count; ++quadrature_point) { + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point); transformation.SetIntPoint(&integration_point); @@ -436,44 +346,34 @@ namespace mean_field::operators { // including the finite-element map type. density_element.CalcPhysShape(transformation, density_shape); - potential_element.CalcPhysShape( - transformation, potential_shape - ); + potential_element.CalcPhysShape(transformation, potential_shape); for (int i = 0; i < density_dof_count; ++i) { data.density_basis(quadrature_point, i) = density_shape(i); } for (int i = 0; i < potential_dof_count; ++i) { - data.potential_basis(quadrature_point, i) = - potential_shape(i); + data.potential_basis(quadrature_point, i) = potential_shape(i); } - const double coefficient_value = - source_coefficient.Eval(transformation, integration_point); + const double coefficient_value = source_coefficient.Eval(transformation, integration_point); transformation.SetIntPoint(&integration_point); - const double quadrature_value = integration_point.weight * - transformation.Weight() * - coefficient_value; + const double quadrature_value = integration_point.weight * transformation.Weight() * coefficient_value; MFEM_VERIFY( std::isfinite(quadrature_value) && quadrature_value > 0.0, "Prepared gravity source operator encountered invalid " "quadrature data on element " - << element_id << ", quadrature point " - << quadrature_point << "." + << element_id << ", quadrature point " << quadrature_point << "." ); data.quadrature_data(quadrature_point) = quadrature_value; } } - MFEM_VERIFY( - !m_elements.empty(), - "PreparedMappedGravitySourceOperator found no stellar elements." - ); + MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements."); m_is_prepared = true; ++m_preparation_count; @@ -483,15 +383,13 @@ namespace mean_field::operators { mfem::Vector &action ) const { MFEM_VERIFY( - m_is_prepared, - "PreparedMappedGravitySourceOperator must be prepared before " - "Mult is called." + m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before " + "Mult is called." ); MFEM_VERIFY( - density_true.Size() == Width(), - "PreparedMappedGravitySourceOperator received a density vector " - "with the wrong size." + density_true.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector " + "with the wrong size." ); mfem::Vector density_local; @@ -509,9 +407,7 @@ namespace mean_field::operators { density_local.GetSubVector(data.density_dofs, element_density); if (data.density_dof_transformation != nullptr) { - data.density_dof_transformation->InvTransformPrimal( - element_density - ); + data.density_dof_transformation->InvTransformPrimal(element_density); } quadrature_density.SetSize(data.quadrature_data.Size()); @@ -527,14 +423,10 @@ namespace mean_field::operators { element_action.SetSize(data.potential_dofs.Size()); // B_potential^T * D * B_density * x_e - data.potential_basis.MultTranspose( - quadrature_density, element_action - ); + data.potential_basis.MultTranspose(quadrature_density, element_action); if (data.potential_dof_transformation != nullptr) { - data.potential_dof_transformation->TransformDual( - element_action - ); + data.potential_dof_transformation->TransformDual(element_action); } local_action.AddElementVector(data.potential_dofs, element_action); @@ -548,22 +440,18 @@ namespace mean_field::operators { mfem::Vector &action ) const { MFEM_VERIFY( - m_is_prepared, - "PreparedMappedGravitySourceOperator must be prepared before " - "MultTranspose is called." + m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before " + "MultTranspose is called." ); MFEM_VERIFY( - potential_true.Size() == Height(), - "PreparedMappedGravitySourceOperator received a potential vector " - "with the wrong size." + potential_true.Size() == Height(), "PreparedMappedGravitySourceOperator received a potential vector " + "with the wrong size." ); mfem::Vector potential_local; - true_to_local( - *m_fem.gravityPotentialFes, potential_true, potential_local - ); + true_to_local(*m_fem.gravityPotentialFes, potential_true, potential_local); mfem::Vector local_action(m_fem.densityFes->GetVSize()); local_action = 0.0; @@ -573,14 +461,10 @@ namespace mean_field::operators { mfem::Vector element_action; for (const ElementPAData &data : m_elements) { - potential_local.GetSubVector( - data.potential_dofs, element_potential - ); + potential_local.GetSubVector(data.potential_dofs, element_potential); if (data.potential_dof_transformation != nullptr) { - data.potential_dof_transformation->InvTransformPrimal( - element_potential - ); + data.potential_dof_transformation->InvTransformPrimal(element_potential); } quadrature_potential.SetSize(data.quadrature_data.Size()); @@ -593,9 +477,7 @@ namespace mean_field::operators { element_action.SetSize(data.density_dofs.Size()); - data.density_basis.MultTranspose( - quadrature_potential, element_action - ); + data.density_basis.MultTranspose(quadrature_potential, element_action); if (data.density_dof_transformation != nullptr) { data.density_dof_transformation->TransformDual(element_action); @@ -610,8 +492,7 @@ namespace mean_field::operators { return m_is_prepared; } - std::uint64_t - PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept { + std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept { return m_preparation_count; } } // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/prepared_hdiv_mass.cpp b/libmeanfield/impl/operators/prepared_hdiv_mass.cpp index 234118a..f469990 100644 --- a/libmeanfield/impl/operators/prepared_hdiv_mass.cpp +++ b/libmeanfield/impl/operators/prepared_hdiv_mass.cpp @@ -10,9 +10,8 @@ import :operators.prepared_hdiv_mass; namespace { int get_operator_size(const mean_field::fem::FEM &f) { MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "PreparedMappedHDivMassOperator requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the " + "gravity-gradient finite-element space." ); return f.gravityFluxFes->GetTrueVSize(); } @@ -24,8 +23,7 @@ namespace { ) { local_vector.SetSize(finite_element_space.GetVSize()); - const mfem::Operator *prolongation = - finite_element_space.GetProlongationMatrix(); + const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(true_vector, local_vector); @@ -41,8 +39,7 @@ namespace { for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { const int attribute = f.mesh->GetAttribute(element_id); - if (attribute > 0 && attribute <= marker.Size() && - marker[attribute - 1] != 0) { + if (attribute > 0 && attribute <= marker.Size() && marker[attribute - 1] != 0) { return element_id; } } @@ -55,23 +52,19 @@ namespace { const mfem::Array &marker, const int representative_element_id ) { - const mfem::FiniteElement &representative_element = - *f.gravityFluxFes->GetFE(representative_element_id); + const mfem::FiniteElement &representative_element = *f.gravityFluxFes->GetFE(representative_element_id); const mfem::ElementTransformation &representative_transformation = *f.mesh->GetElementTransformation(representative_element_id); for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { const int attribute = f.mesh->GetAttribute(element_id); - if (attribute <= 0 || attribute > marker.Size() || - marker[attribute - 1] == 0) { + if (attribute <= 0 || attribute > marker.Size() || marker[attribute - 1] == 0) { continue; } - const mfem::FiniteElement &element = - *f.gravityFluxFes->GetFE(element_id); - const mfem::ElementTransformation &transformation = - *f.mesh->GetElementTransformation(element_id); + const mfem::FiniteElement &element = *f.gravityFluxFes->GetFE(element_id); + const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(element_id); MFEM_VERIFY( element.GetGeomType() == representative_element.GetGeomType(), @@ -85,16 +78,14 @@ namespace { "finite-element order." ); MFEM_VERIFY( - transformation.OrderW() == - representative_transformation.OrderW(), + transformation.OrderW() == representative_transformation.OrderW(), "Prepared H(div) mass domains currently require a uniform " "geometry-weight order." ); } } - class FrozenMappedHDivMassCoefficient final - : public mfem::MatrixCoefficient { + class FrozenMappedHDivMassCoefficient final : public mfem::MatrixCoefficient { public: FrozenMappedHDivMassCoefficient( const mean_field::fem::FEM &f, @@ -107,9 +98,7 @@ namespace { m_domain_mapper(domain_mapper), m_workspace(domain_mapper.GetDimension()), m_elevates_vacuum(elevates_vacuum) { - true_to_local( - *m_fem.displacementFes, displacement_true, m_displacement_local - ); + true_to_local(*m_fem.displacementFes, displacement_true, m_displacement_local); } void Eval( @@ -125,9 +114,7 @@ namespace { "Mapped H(div) mass coefficient received an invalid element ID." ); - const bool element_is_vacuum = - transformation.Attribute == - m_domain_mapper.GetVacuumElementAttribute(); + const bool element_is_vacuum = transformation.Attribute == m_domain_mapper.GetVacuumElementAttribute(); if (element_is_vacuum != m_elevates_vacuum) { mass_tensor.SetSize(m_domain_mapper.GetDimension()); @@ -138,34 +125,27 @@ namespace { LoadElement(element_id); const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = *m_displacement_data, - .compactification = *m_compactification_data + .displacement = *m_displacement_data, .compactification = *m_compactification_data }; mean_field::mapping::VolumeMappingContext mapping_context; - const mean_field::mapping::MappingStatus status = - m_domain_mapper.EvaluateVolume( - mapping_data, transformation, integration_point, - m_workspace, mapping_context - ); + const mean_field::mapping::MappingStatus status = m_domain_mapper.EvaluateVolume( + mapping_data, transformation, integration_point, m_workspace, mapping_context + ); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Stateless domain mapping failed while preparing the H(div) " "mass " "operator. Mapping status = " - << static_cast(status) - << ", element ID = " << element_id + << static_cast(status) << ", element ID = " << element_id << ", element attribute = " << transformation.Attribute - << ", coefficient domain = " - << (m_elevates_vacuum ? "vacuum" : "stellar") + << ", coefficient domain = " << (m_elevates_vacuum ? "vacuum" : "stellar") ); - const mfem::DenseMatrix &mapping_jacobian = - mapping_context.mapping.mapping_jacobian; - const double mapping_determinant = - mapping_context.mapping.mapping_determinant; + const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping.mapping_jacobian; + const double mapping_determinant = mapping_context.mapping.mapping_determinant; MFEM_VERIFY( std::isfinite(mapping_determinant) && mapping_determinant > 0.0, @@ -183,48 +163,32 @@ namespace { return; } - const mfem::FiniteElement &displacement_element = - *m_fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *m_fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); mfem::DofTransformation *displacement_dof_transformation = - m_fem.displacementFes->GetElementVDofs( - element_id, m_displacement_dofs - ); + m_fem.displacementFes->GetElementVDofs(element_id, m_displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - m_fem.compactificationFes->GetElementDofs( - element_id, m_compactification_dofs - ); + m_fem.compactificationFes->GetElementDofs(element_id, m_compactification_dofs); - m_displacement_local.GetSubVector( - m_displacement_dofs, m_element_displacement - ); - m_fem.compactificationCoordinate->GetSubVector( - m_compactification_dofs, m_element_compactification - ); + m_displacement_local.GetSubVector(m_displacement_dofs, m_element_displacement); + m_fem.compactificationCoordinate->GetSubVector(m_compactification_dofs, m_element_compactification); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - m_element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(m_element_displacement); } if (compactification_dof_transformation != nullptr) { - compactification_dof_transformation->InvTransformPrimal( - m_element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(m_element_compactification); } - m_displacement_data = std::make_unique< - mean_field::mapping::ElementDisplacementData>( + m_displacement_data = std::make_unique( mean_field::mapping::ElementDisplacementDataFromElementVDofs( displacement_element, m_element_displacement ) ); - m_compactification_data = std::make_unique< - mean_field::mapping::ElementCompactificationData>( + m_compactification_data = std::make_unique( compactification_element, m_element_compactification ); @@ -242,10 +206,8 @@ namespace { mfem::Vector m_element_displacement; mfem::Vector m_element_compactification; - std::unique_ptr - m_displacement_data; - std::unique_ptr - m_compactification_data; + std::unique_ptr m_displacement_data; + std::unique_ptr m_compactification_data; mean_field::mapping::DomainMapperStateless::Workspace m_workspace; int m_cached_element_id{-1}; @@ -261,33 +223,26 @@ namespace mean_field::operators { : Operator(get_operator_size(f)), m_fem(f), m_domain_mapper(domain_mapper) { + MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh."); MFEM_VERIFY( - f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh." + f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "PreparedMappedHDivMassOperator requires the " - "gravity-gradient finite-element space." + f.displacementFes != nullptr, "PreparedMappedHDivMassOperator requires the " + "displacement finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "PreparedMappedHDivMassOperator requires the " - "displacement finite-element space." + f.compactificationFes != nullptr, "PreparedMappedHDivMassOperator requires the compactification " + "finite-element space." ); MFEM_VERIFY( - f.compactificationFes != nullptr, - "PreparedMappedHDivMassOperator requires the compactification " - "finite-element space." + f.compactificationCoordinate != nullptr, "PreparedMappedHDivMassOperator requires the compactification " + "coordinate." ); MFEM_VERIFY( - f.compactificationCoordinate != nullptr, - "PreparedMappedHDivMassOperator requires the compactification " - "coordinate." - ); - MFEM_VERIFY( - f.quadratureFactory != nullptr, - "PreparedMappedHDivMassOperator requires the quadrature-rule " - "factory." + f.quadratureFactory != nullptr, "PreparedMappedHDivMassOperator requires the quadrature-rule " + "factory." ); MFEM_VERIFY( domain_mapper.GetDimension() == f.mesh->Dimension(), @@ -295,39 +250,26 @@ namespace mean_field::operators { "dimension." ); - utils::populate_element_mask( - f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker - ); - utils::populate_element_mask( - f.mesh.get(), utils::DOMAINS::VACUUM, m_vacuum_marker - ); + utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker); + utils::populate_element_mask(f.mesh.get(), utils::DOMAINS::VACUUM, m_vacuum_marker); - const int stellar_element_id = - find_representative_element(f, m_stellar_marker); - const int vacuum_element_id = - find_representative_element(f, m_vacuum_marker); + const int stellar_element_id = find_representative_element(f, m_stellar_marker); + const int vacuum_element_id = find_representative_element(f, m_vacuum_marker); MFEM_VERIFY( stellar_element_id >= 0, "PreparedMappedHDivMassOperator requires " "at least one stellar element." ); MFEM_VERIFY( - vacuum_element_id >= 0, - "PreparedMappedHDivMassOperator requires at " - "least one compactified vacuum element." + vacuum_element_id >= 0, "PreparedMappedHDivMassOperator requires at " + "least one compactified vacuum element." ); - validate_uniform_domain_discretization( - f, m_stellar_marker, stellar_element_id - ); - validate_uniform_domain_discretization( - f, m_vacuum_marker, vacuum_element_id - ); + validate_uniform_domain_discretization(f, m_stellar_marker, stellar_element_id); + validate_uniform_domain_discretization(f, m_vacuum_marker, vacuum_element_id); } - void PreparedMappedHDivMassOperator::Prepare( - const mfem::Vector &displacement_true - ) { + void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement_true) { MFEM_VERIFY( displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(), "PreparedMappedHDivMassOperator received a displacement vector " @@ -337,71 +279,48 @@ namespace mean_field::operators { for (int i = 0; i < displacement_true.Size(); ++i) { MFEM_VERIFY( - std::isfinite(displacement_true(i)), - "PreparedMappedHDivMassOperator received a non-finite " - "displacement " - "value." + std::isfinite(displacement_true(i)), "PreparedMappedHDivMassOperator received a non-finite " + "displacement " + "value." ); } - const int stellar_element_id = - find_representative_element(m_fem, m_stellar_marker); - const int vacuum_element_id = - find_representative_element(m_fem, m_vacuum_marker); + const int stellar_element_id = find_representative_element(m_fem, m_stellar_marker); + const int vacuum_element_id = find_representative_element(m_fem, m_vacuum_marker); - const mfem::FiniteElement &stellar_element = - *m_fem.gravityFluxFes->GetFE(stellar_element_id); - const mfem::FiniteElement &vacuum_element = - *m_fem.gravityFluxFes->GetFE(vacuum_element_id); + const mfem::FiniteElement &stellar_element = *m_fem.gravityFluxFes->GetFE(stellar_element_id); + const mfem::FiniteElement &vacuum_element = *m_fem.gravityFluxFes->GetFE(vacuum_element_id); - mfem::ElementTransformation &stellar_transformation = - *m_fem.mesh->GetElementTransformation(stellar_element_id); - mfem::ElementTransformation &vacuum_transformation = - *m_fem.mesh->GetElementTransformation(vacuum_element_id); + mfem::ElementTransformation &stellar_transformation = *m_fem.mesh->GetElementTransformation(stellar_element_id); + mfem::ElementTransformation &vacuum_transformation = *m_fem.mesh->GetElementTransformation(vacuum_element_id); m_mass_form.reset(); m_stellar_mass_coefficient.reset(); m_vacuum_mass_coefficient.reset(); m_stellar_mass_coefficient = - std::make_unique( - m_fem, m_domain_mapper, displacement_true, false - ); + std::make_unique(m_fem, m_domain_mapper, displacement_true, false); m_vacuum_mass_coefficient = - std::make_unique( - m_fem, m_domain_mapper, displacement_true, true - ); + std::make_unique(m_fem, m_domain_mapper, displacement_true, true); - m_mass_form = - std::make_unique(m_fem.gravityFluxFes.get()); + m_mass_form = std::make_unique(m_fem.gravityFluxFes.get()); m_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); - auto stellar_integrator = - std::make_unique( - *m_stellar_mass_coefficient - ); - auto vacuum_integrator = std::make_unique( - *m_vacuum_mass_coefficient + auto stellar_integrator = std::make_unique(*m_stellar_mass_coefficient); + auto vacuum_integrator = std::make_unique(*m_vacuum_mass_coefficient); + + m_fem.quadratureFactory->configure_gravity_hdiv_mass( + *stellar_integrator, quadrature::QuadratureRole::discretization, stellar_element, stellar_transformation, + utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); m_fem.quadratureFactory->configure_gravity_hdiv_mass( - *stellar_integrator, quadrature::QuadratureRole::discretization, - stellar_element, stellar_transformation, utils::DOMAINS::STELLAR, - quadrature::MappingKind::general + *vacuum_integrator, quadrature::QuadratureRole::discretization, vacuum_element, vacuum_transformation, + utils::DOMAINS::VACUUM, quadrature::MappingKind::kelvin ); - m_fem.quadratureFactory->configure_gravity_hdiv_mass( - *vacuum_integrator, quadrature::QuadratureRole::discretization, - vacuum_element, vacuum_transformation, utils::DOMAINS::VACUUM, - quadrature::MappingKind::kelvin - ); - - m_mass_form->AddDomainIntegrator( - stellar_integrator.release(), m_stellar_marker - ); - m_mass_form->AddDomainIntegrator( - vacuum_integrator.release(), m_vacuum_marker - ); + m_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_marker); + m_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_vacuum_marker); m_mass_form->Assemble(); m_is_prepared = true; @@ -434,8 +353,7 @@ namespace mean_field::operators { return m_is_prepared; } - std::uint64_t - PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept { + std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept { return m_preparation_count; } } // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp b/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp index 9454c78..7f4f6fb 100644 --- a/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp +++ b/libmeanfield/impl/operators/prepared_hydrostatic_equilibrium.cpp @@ -18,14 +18,12 @@ namespace { mfem::Vector &localVector ) { MFEM_VERIFY( - trueVector.Size() == finiteElementSpace.GetTrueVSize(), - "Hydrostatic true vector has the wrong size." + trueVector.Size() == finiteElementSpace.GetTrueVSize(), "Hydrostatic true vector has the wrong size." ); localVector.SetSize(finiteElementSpace.GetVSize()); - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->Mult(trueVector, localVector); @@ -40,15 +38,13 @@ namespace { mfem::Vector &trueVector ) { MFEM_VERIFY( - localVector.Size() == finiteElementSpace.GetVSize(), - "Hydrostatic local vector has the wrong size." + localVector.Size() == finiteElementSpace.GetVSize(), "Hydrostatic local vector has the wrong size." ); trueVector.SetSize(finiteElementSpace.GetTrueVSize()); - trueVector = 0.0; + trueVector = 0.0; - const mfem::Operator *prolongation = - finiteElementSpace.GetProlongationMatrix(); + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); if (prolongation != nullptr) { prolongation->MultTranspose(localVector, trueVector); @@ -64,9 +60,8 @@ namespace { mfem::Vector &block ) { MFEM_VERIFY( - offset >= 0 && size >= 0 && offset + size <= source.Size(), - "Hydrostatic Jacobian block lies outside the " - "input vector." + offset >= 0 && size >= 0 && offset + size <= source.Size(), "Hydrostatic Jacobian block lies outside the " + "input vector." ); block.SetSize(size); @@ -82,78 +77,55 @@ namespace { const mfem::FiniteElement &gravityPotentialElement, const mfem::ElementTransformation &transformation ) { - using EnthalpyField = - mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; MFEM_VERIFY( - enthalpyElement.GetOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder, + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, "The prepared hydrostatic enthalpy element does " "not match the registered field." ); MFEM_VERIFY( - gravityPotentialElement.GetOrder() == - mean_field::field::Gravity::Potential::familyOrder, + gravityPotentialElement.GetOrder() == mean_field::field::Gravity::Potential::familyOrder, "The prepared hydrostatic potential element does " "not match the registered field." ); - const auto enthalpyQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto enthalpyQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - const auto gravityQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumGravity>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto gravityQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); // A rigid-rotation potential is quadratic in physical position. - const auto rotationQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumRotation>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), std::array{2}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto rotationQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array{2}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - const auto constantQuery = EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::EquilibriumConstant>( - mean_field::quadrature::QuadratureRole::discretization, - transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::general + const auto constantQuery = EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); const std::array candidateRules{ - f.quadratureFactory->get( - enthalpyQuery, transformation.GetGeometryType() - ), - f.quadratureFactory->get( - gravityQuery, transformation.GetGeometryType() - ), - f.quadratureFactory->get( - rotationQuery, transformation.GetGeometryType() - ), - f.quadratureFactory->get( - constantQuery, transformation.GetGeometryType() - ) + f.quadratureFactory->get(enthalpyQuery, transformation.GetGeometryType()), + f.quadratureFactory->get(gravityQuery, transformation.GetGeometryType()), + f.quadratureFactory->get(rotationQuery, transformation.GetGeometryType()), + f.quadratureFactory->get(constantQuery, transformation.GetGeometryType()) }; - const auto selectedRule = std::max_element( - candidateRules.begin(), candidateRules.end(), - [](const auto &left, const auto &right) { + const auto selectedRule = + std::max_element(candidateRules.begin(), candidateRules.end(), [](const auto &left, const auto &right) { return left.resolution.order < right.resolution.order; - } - ); + }); MFEM_VERIFY( - selectedRule != candidateRules.end() && - selectedRule->integration_rule != nullptr, + selectedRule != candidateRules.end() && selectedRule->integration_rule != nullptr, "The quadrature policy did not return a valid " "hydrostatic-equilibrium integration rule." ); @@ -163,25 +135,20 @@ namespace { } // namespace namespace mean_field::operators { - HydrostaticJacobianBlockLayout::HydrostaticJacobianBlockLayout( - const fem::FEM &f - ) { + HydrostaticJacobianBlockLayout::HydrostaticJacobianBlockLayout(const fem::FEM &f) { MFEM_VERIFY( - f.enthalpyFes != nullptr, - "HydrostaticJacobianBlockLayout requires the " - "enthalpy finite-element space." + f.enthalpyFes != nullptr, "HydrostaticJacobianBlockLayout requires the " + "enthalpy finite-element space." ); MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "HydrostaticJacobianBlockLayout requires the " - "gravity-potential finite-element space." + f.gravityPotentialFes != nullptr, "HydrostaticJacobianBlockLayout requires the " + "gravity-potential finite-element space." ); MFEM_VERIFY( - f.displacementFes != nullptr, - "HydrostaticJacobianBlockLayout requires the " - "displacement finite-element space." + f.displacementFes != nullptr, "HydrostaticJacobianBlockLayout requires the " + "displacement finite-element space." ); m_enthalpySize = f.enthalpyFes->GetTrueVSize(); @@ -189,20 +156,16 @@ namespace mean_field::operators { m_displacementSize = f.displacementFes->GetTrueVSize(); m_residualSize = m_enthalpySize; - m_totalSize = - m_enthalpySize + m_gravityPotentialSize + 1 + m_displacementSize; + m_totalSize = m_enthalpySize + m_gravityPotentialSize + 1 + m_displacementSize; MFEM_VERIFY( - m_enthalpySize > 0 && m_gravityPotentialSize > 0 && - m_displacementSize > 0, + m_enthalpySize > 0 && m_gravityPotentialSize > 0 && m_displacementSize > 0, "HydrostaticJacobianBlockLayout received an empty " "finite-element space." ); } - int HydrostaticJacobianBlockLayout::Offset( - const HydrostaticJacobianInputBlock block - ) const { + int HydrostaticJacobianBlockLayout::Offset(const HydrostaticJacobianInputBlock block) const { switch (block) { case HydrostaticJacobianInputBlock::enthalpy: return 0; @@ -225,9 +188,7 @@ namespace mean_field::operators { return 0; } - int HydrostaticJacobianBlockLayout::Size( - const HydrostaticJacobianInputBlock block - ) const { + int HydrostaticJacobianBlockLayout::Size(const HydrostaticJacobianInputBlock block) const { switch (block) { case HydrostaticJacobianInputBlock::enthalpy: return m_enthalpySize; @@ -258,75 +219,61 @@ namespace mean_field::operators { return m_residualSize; } - PreparedHydrostaticEquilibriumOperator:: - PreparedHydrostaticEquilibriumOperator( - const fem::FEM &f, - const mapping::DomainMapperStateless &domainMapper - ) + PreparedHydrostaticEquilibriumOperator::PreparedHydrostaticEquilibriumOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper + ) : m_fem(f), m_domainMapper(domainMapper), m_context( f, domainMapper ) { + MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedHydrostaticEquilibriumOperator requires a mesh."); + MFEM_VERIFY( - m_fem.mesh != nullptr, - "PreparedHydrostaticEquilibriumOperator requires a mesh." + m_fem.enthalpyFes != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the enthalpy finite-element space." ); MFEM_VERIFY( - m_fem.enthalpyFes != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the enthalpy finite-element space." + m_fem.gravityPotentialFes != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the gravity-potential finite-element space." ); MFEM_VERIFY( - m_fem.gravityPotentialFes != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the gravity-potential finite-element space." + m_fem.displacementFes != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the displacement finite-element space." ); MFEM_VERIFY( - m_fem.displacementFes != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the displacement finite-element space." + m_fem.compactificationFes != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the compactification finite-element space." ); MFEM_VERIFY( - m_fem.compactificationFes != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the compactification finite-element space." + m_fem.compactificationCoordinate != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the compactification coordinate." ); MFEM_VERIFY( - m_fem.compactificationCoordinate != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the compactification coordinate." + m_fem.quadratureFactory != nullptr, "PreparedHydrostaticEquilibriumOperator requires " + "the quadrature-rule factory." ); MFEM_VERIFY( - m_fem.quadratureFactory != nullptr, - "PreparedHydrostaticEquilibriumOperator requires " - "the quadrature-rule factory." - ); - - MFEM_VERIFY( - m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), - "The hydrostatic operator's stateless mapper " - "dimension does not match the mesh dimension." + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), "The hydrostatic operator's stateless mapper " + "dimension does not match the mesh dimension." ); } - PreparedHydrostaticEquilibriumReport - PreparedHydrostaticEquilibriumOperator::Prepare( + PreparedHydrostaticEquilibriumReport PreparedHydrostaticEquilibriumOperator::Prepare( const context::hydrostatic::HydrostaticEquilibriumStateView &state, - const context::hydrostatic::HydrostaticEquilibriumDependencies - &dependencies, + const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies, const physics::RigidRotation &rotation ) { const bool rotationObjectChanged = - !m_context.IsPrepared() || - dependencies.rotation != m_context.GetDependencies().rotation; + !m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation; PreparedHydrostaticEquilibriumReport report; @@ -338,9 +285,8 @@ namespace mean_field::operators { } MFEM_VERIFY( - m_rotation.has_value(), - "The prepared hydrostatic operator has no frozen " - "rotation state." + m_rotation.has_value(), "The prepared hydrostatic operator has no frozen " + "rotation state." ); m_isPrepared = false; @@ -369,9 +315,8 @@ namespace mean_field::operators { } MFEM_VERIFY( - !m_elements.empty(), - "PreparedHydrostaticEquilibriumOperator found no " - "stellar elements." + !m_elements.empty(), "PreparedHydrostaticEquilibriumOperator found no " + "stellar elements." ); MFEM_VERIFY( @@ -393,89 +338,68 @@ namespace mean_field::operators { mfem::Vector gravityPotentialShape; for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) { - mfem::ElementTransformation *transformation = - m_fem.mesh->GetElementTransformation(elementId); + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId); MFEM_VERIFY( - transformation != nullptr, - "Prepared hydrostatic static planning received " - "a null element transformation." + transformation != nullptr, "Prepared hydrostatic static planning received " + "a null element transformation." ); if (transformation->Attribute == vacuumAttribute) { continue; } - const mfem::FiniteElement &enthalpyElement = - *m_fem.enthalpyFes->GetFE(elementId); + const mfem::FiniteElement &enthalpyElement = *m_fem.enthalpyFes->GetFE(elementId); - const mfem::FiniteElement &gravityPotentialElement = - *m_fem.gravityPotentialFes->GetFE(elementId); + const mfem::FiniteElement &gravityPotentialElement = *m_fem.gravityPotentialFes->GetFE(elementId); MFEM_VERIFY( - enthalpyElement.GetGeomType() == - gravityPotentialElement.GetGeomType() && - enthalpyElement.GetGeomType() == - transformation->GetGeometryType(), + enthalpyElement.GetGeomType() == gravityPotentialElement.GetGeomType() && + enthalpyElement.GetGeomType() == transformation->GetGeometryType(), "Hydrostatic element geometries do not agree." ); m_elements.emplace_back(); - ElementPAData &data = m_elements.back(); - data.elementId = elementId; + ElementPAData &data = m_elements.back(); + data.elementId = elementId; - data.enthalpyDofTransformation = - m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs); + data.enthalpyDofTransformation = m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs); data.gravityPotentialDofTransformation = - m_fem.gravityPotentialFes->GetElementDofs( - elementId, data.gravityPotentialDofs - ); + m_fem.gravityPotentialFes->GetElementDofs(elementId, data.gravityPotentialDofs); data.displacementDofTransformation = - m_fem.displacementFes->GetElementVDofs( - elementId, data.displacementDofs - ); + m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs); - data.integrationRule = &get_hydrostatic_rule( - m_fem, enthalpyElement, gravityPotentialElement, *transformation - ); + data.integrationRule = + &get_hydrostatic_rule(m_fem, enthalpyElement, gravityPotentialElement, *transformation); - const int quadraturePointCount = data.integrationRule->GetNPoints(); + const int quadraturePointCount = data.integrationRule->GetNPoints(); - const int enthalpyDofCount = enthalpyElement.GetDof(); + const int enthalpyDofCount = enthalpyElement.GetDof(); - const int gravityPotentialDofCount = - gravityPotentialElement.GetDof(); + const int gravityPotentialDofCount = gravityPotentialElement.GetDof(); data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount); - data.gravityPotentialBasis.SetSize( - quadraturePointCount, gravityPotentialDofCount - ); + data.gravityPotentialBasis.SetSize(quadraturePointCount, gravityPotentialDofCount); enthalpyShape.SetSize(enthalpyDofCount); gravityPotentialShape.SetSize(gravityPotentialDofCount); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - data.integrationRule->IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); enthalpyElement.CalcShape(integrationPoint, enthalpyShape); - gravityPotentialElement.CalcShape( - integrationPoint, gravityPotentialShape - ); + gravityPotentialElement.CalcShape(integrationPoint, gravityPotentialShape); for (int dof = 0; dof < enthalpyDofCount; ++dof) { - data.enthalpyBasis(quadraturePoint, dof) = - enthalpyShape(dof); + data.enthalpyBasis(quadraturePoint, dof) = enthalpyShape(dof); } for (int dof = 0; dof < gravityPotentialDofCount; ++dof) { - data.gravityPotentialBasis(quadraturePoint, dof) = - gravityPotentialShape(dof); + data.gravityPotentialBasis(quadraturePoint, dof) = gravityPotentialShape(dof); } } } @@ -484,14 +408,9 @@ namespace mean_field::operators { void PreparedHydrostaticEquilibriumOperator::PrepareGeometry() { mfem::Vector displacementLocal; - true_to_local( - *m_fem.displacementFes, m_context.GetDisplacementTrue(), - displacementLocal - ); + true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), displacementLocal); - mapping::DomainMapperStateless::Workspace workspace( - m_fem.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mfem::Array compactificationDofs; @@ -499,8 +418,7 @@ namespace mean_field::operators { mfem::Vector elementCompactification; for (ElementPAData &data : m_elements) { - mfem::ElementTransformation *transformation = - m_fem.mesh->GetElementTransformation(data.elementId); + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); MFEM_VERIFY( transformation != nullptr && data.integrationRule != nullptr, @@ -509,89 +427,62 @@ namespace mean_field::operators { ); mfem::DofTransformation *compactificationDofTransformation = - m_fem.compactificationFes->GetElementDofs( - data.elementId, compactificationDofs - ); + m_fem.compactificationFes->GetElementDofs(data.elementId, compactificationDofs); - displacementLocal.GetSubVector( - data.displacementDofs, elementDisplacement - ); + displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement); - m_fem.compactificationCoordinate->GetSubVector( - compactificationDofs, elementCompactification - ); + m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); if (data.displacementDofTransformation != nullptr) { - data.displacementDofTransformation->InvTransformPrimal( - elementDisplacement - ); + data.displacementDofTransformation->InvTransformPrimal(elementDisplacement); } if (compactificationDofTransformation != nullptr) { - compactificationDofTransformation->InvTransformPrimal( - elementCompactification - ); + compactificationDofTransformation->InvTransformPrimal(elementCompactification); } - const mfem::FiniteElement &displacementElement = - *m_fem.displacementFes->GetFE(data.elementId); + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); - const mfem::FiniteElement &compactificationElement = - *m_fem.compactificationFes->GetFE(data.elementId); + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId); data.baseDisplacementData.emplace( - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacement - ) + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement) ); - data.compactificationData.emplace( - compactificationElement, elementCompactification - ); + data.compactificationData.emplace(compactificationElement, elementCompactification); const mapping::ElementMappingData mappingData{ - .displacement = *data.baseDisplacementData, - .compactification = *data.compactificationData + .displacement = *data.baseDisplacementData, .compactification = *data.compactificationData }; const int quadraturePointCount = data.integrationRule->GetNPoints(); - data.physicalPositions.SetSize( - quadraturePointCount, m_fem.mesh->Dimension() - ); + data.physicalPositions.SetSize(quadraturePointCount, m_fem.mesh->Dimension()); data.quadratureWeights.SetSize(quadraturePointCount); data.baseMappingContexts.resize(quadraturePointCount); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - data.integrationRule->IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); transformation->SetIntPoint(&integrationPoint); - mapping::VolumeMappingContext &mappingContext = - data.baseMappingContexts[quadraturePoint]; + mapping::VolumeMappingContext &mappingContext = data.baseMappingContexts[quadraturePoint]; - const mapping::MappingStatus mappingStatus = - m_domainMapper.EvaluateVolume( - mappingData, *transformation, integrationPoint, - workspace, mappingContext - ); + const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); MFEM_VERIFY( mappingStatus == mapping::MappingStatus::valid, "Stateless mapping failed while preparing " "hydrostatic geometry. Element: " - << data.elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadraturePoint - << ", status: " << static_cast(mappingStatus) + << data.elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) ); - const double quadratureWeight = - mappingContext.quadrature.weight; + const double quadratureWeight = mappingContext.quadrature.weight; MFEM_VERIFY( std::isfinite(quadratureWeight) && quadratureWeight > 0.0, @@ -601,33 +492,27 @@ namespace mean_field::operators { data.quadratureWeights(quadraturePoint) = quadratureWeight; - for (int component = 0; component < m_fem.mesh->Dimension(); - ++component) { - const double position = - mappingContext.mapping.physical_position(component); + for (int component = 0; component < m_fem.mesh->Dimension(); ++component) { + const double position = mappingContext.mapping.physical_position(component); MFEM_VERIFY( - std::isfinite(position), - "Prepared hydrostatic geometry encountered " - "a non-finite physical position." + std::isfinite(position), "Prepared hydrostatic geometry encountered " + "a non-finite physical position." ); - data.physicalPositions(quadraturePoint, component) = - position; + data.physicalPositions(quadraturePoint, component) = position; } } } } - void - PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() { + void PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() { for (ElementPAData &data : m_elements) { - const int quadraturePointCount = data.quadratureWeights.Size(); + const int quadraturePointCount = data.quadratureWeights.Size(); - const int enthalpyDofCount = data.enthalpyBasis.Width(); + const int enthalpyDofCount = data.enthalpyBasis.Width(); - const int gravityPotentialDofCount = - data.gravityPotentialBasis.Width(); + const int gravityPotentialDofCount = data.gravityPotentialBasis.Width(); MFEM_VERIFY( data.enthalpyBasis.Height() == quadraturePointCount && @@ -638,9 +523,7 @@ namespace mean_field::operators { data.enthalpyJacobian.SetSize(enthalpyDofCount, enthalpyDofCount); - data.gravityPotentialJacobian.SetSize( - enthalpyDofCount, gravityPotentialDofCount - ); + data.gravityPotentialJacobian.SetSize(enthalpyDofCount, gravityPotentialDofCount); data.bernoulliConstantJacobian.SetSize(enthalpyDofCount); @@ -648,32 +531,22 @@ namespace mean_field::operators { data.gravityPotentialJacobian = 0.0; data.bernoulliConstantJacobian = 0.0; - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const double quadratureWeight = - data.quadratureWeights(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const double quadratureWeight = data.quadratureWeights(quadraturePoint); for (int testDof = 0; testDof < enthalpyDofCount; ++testDof) { - const double weightedTestBasis = - quadratureWeight * - data.enthalpyBasis(quadraturePoint, testDof); + const double weightedTestBasis = quadratureWeight * data.enthalpyBasis(quadraturePoint, testDof); - data.bernoulliConstantJacobian(testDof) -= - weightedTestBasis; + data.bernoulliConstantJacobian(testDof) -= weightedTestBasis; - for (int trialDof = 0; trialDof < enthalpyDofCount; - ++trialDof) { + for (int trialDof = 0; trialDof < enthalpyDofCount; ++trialDof) { data.enthalpyJacobian(testDof, trialDof) += - weightedTestBasis * - data.enthalpyBasis(quadraturePoint, trialDof); + weightedTestBasis * data.enthalpyBasis(quadraturePoint, trialDof); } - for (int trialDof = 0; trialDof < gravityPotentialDofCount; - ++trialDof) { + for (int trialDof = 0; trialDof < gravityPotentialDofCount; ++trialDof) { data.gravityPotentialJacobian(testDof, trialDof) += - weightedTestBasis * data.gravityPotentialBasis( - quadraturePoint, trialDof - ); + weightedTestBasis * data.gravityPotentialBasis(quadraturePoint, trialDof); } } } @@ -683,10 +556,7 @@ namespace mean_field::operators { } void PreparedHydrostaticEquilibriumOperator::PrepareRotation() { - MFEM_VERIFY( - m_rotation.has_value(), - "Prepared hydrostatic rotation has no frozen state." - ); + MFEM_VERIFY(m_rotation.has_value(), "Prepared hydrostatic rotation has no frozen state."); mfem::Vector physicalPosition(m_fem.mesh->Dimension()); @@ -696,54 +566,41 @@ namespace mean_field::operators { const int quadraturePointCount = data.physicalPositions.Height(); MFEM_VERIFY( - data.physicalPositions.Width() == m_fem.mesh->Dimension(), - "Prepared hydrostatic rotation has invalid " - "geometry data." + data.physicalPositions.Width() == m_fem.mesh->Dimension(), "Prepared hydrostatic rotation has invalid " + "geometry data." ); data.rotationPotential.SetSize(quadraturePointCount); - data.rotationGradient.SetSize( - quadraturePointCount, m_fem.mesh->Dimension() - ); + data.rotationGradient.SetSize(quadraturePointCount, m_fem.mesh->Dimension()); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - for (int component = 0; component < physicalPosition.Size(); - ++component) { - physicalPosition(component) = - data.physicalPositions(quadraturePoint, component); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + for (int component = 0; component < physicalPosition.Size(); ++component) { + physicalPosition(component) = data.physicalPositions(quadraturePoint, component); } - const double rotationPotential = - m_rotation->potential(physicalPosition); + const double rotationPotential = m_rotation->potential(physicalPosition); MFEM_VERIFY( - std::isfinite(rotationPotential), - "Prepared hydrostatic rotation encountered " - "a non-finite potential." + std::isfinite(rotationPotential), "Prepared hydrostatic rotation encountered " + "a non-finite potential." ); data.rotationPotential(quadraturePoint) = rotationPotential; - for (int component = 0; component < physicalPosition.Size(); - ++component) { + for (int component = 0; component < physicalPosition.Size(); ++component) { coordinateDirection = 0.0; coordinateDirection(component) = 1.0; const double gradientComponent = - m_rotation->potential_directional_derivative( - physicalPosition, coordinateDirection - ); + m_rotation->potential_directional_derivative(physicalPosition, coordinateDirection); MFEM_VERIFY( - std::isfinite(gradientComponent), - "Prepared hydrostatic rotation encountered " - "a non-finite potential gradient." + std::isfinite(gradientComponent), "Prepared hydrostatic rotation encountered " + "a non-finite potential gradient." ); - data.rotationGradient(quadraturePoint, component) = - gradientComponent; + data.rotationGradient(quadraturePoint, component) = gradientComponent; } } } @@ -753,14 +610,9 @@ namespace mean_field::operators { mfem::Vector enthalpyLocal; mfem::Vector gravityPotentialLocal; - true_to_local( - *m_fem.enthalpyFes, m_context.GetBaseEnthalpyTrue(), enthalpyLocal - ); + true_to_local(*m_fem.enthalpyFes, m_context.GetBaseEnthalpyTrue(), enthalpyLocal); - true_to_local( - *m_fem.gravityPotentialFes, m_context.GetBaseGravityPotentialTrue(), - gravityPotentialLocal - ); + true_to_local(*m_fem.gravityPotentialFes, m_context.GetBaseGravityPotentialTrue(), gravityPotentialLocal); mfem::Vector elementEnthalpy; mfem::Vector elementGravityPotential; @@ -770,20 +622,14 @@ namespace mean_field::operators { for (ElementPAData &data : m_elements) { enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy); - gravityPotentialLocal.GetSubVector( - data.gravityPotentialDofs, elementGravityPotential - ); + gravityPotentialLocal.GetSubVector(data.gravityPotentialDofs, elementGravityPotential); if (data.enthalpyDofTransformation != nullptr) { - data.enthalpyDofTransformation->InvTransformPrimal( - elementEnthalpy - ); + data.enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy); } if (data.gravityPotentialDofTransformation != nullptr) { - data.gravityPotentialDofTransformation->InvTransformPrimal( - elementGravityPotential - ); + data.gravityPotentialDofTransformation->InvTransformPrimal(elementGravityPotential); } const int quadraturePointCount = data.quadratureWeights.Size(); @@ -794,35 +640,27 @@ namespace mean_field::operators { data.enthalpyBasis.Mult(elementEnthalpy, quadratureEnthalpy); - data.gravityPotentialBasis.Mult( - elementGravityPotential, quadratureGravityPotential - ); + data.gravityPotentialBasis.Mult(elementGravityPotential, quadratureGravityPotential); MFEM_VERIFY( - data.rotationPotential.Size() == quadraturePointCount, - "Prepared hydrostatic base state has stale " - "rotation data." + data.rotationPotential.Size() == quadraturePointCount, "Prepared hydrostatic base state has stale " + "rotation data." ); data.weightedResidual.SetSize(quadraturePointCount); data.hydrostaticImbalance.SetSize(quadraturePointCount); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const double imbalance = - quadratureEnthalpy(quadraturePoint) + - quadratureGravityPotential(quadraturePoint) - - data.rotationPotential(quadraturePoint) - - m_context.GetBernoulliConstant(); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const double imbalance = quadratureEnthalpy(quadraturePoint) + + quadratureGravityPotential(quadraturePoint) - + data.rotationPotential(quadraturePoint) - m_context.GetBernoulliConstant(); - const double weightedResidual = - data.quadratureWeights(quadraturePoint) * imbalance; + const double weightedResidual = data.quadratureWeights(quadraturePoint) * imbalance; MFEM_VERIFY( - std::isfinite(weightedResidual), - "Prepared hydrostatic base state encountered " - "a non-finite residual value." + std::isfinite(weightedResidual), "Prepared hydrostatic base state encountered " + "a non-finite residual value." ); data.weightedResidual(quadraturePoint) = weightedResidual; @@ -832,18 +670,15 @@ namespace mean_field::operators { } } - void PreparedHydrostaticEquilibriumOperator:: - FinalizeDisplacementJacobianPreparation() { + void PreparedHydrostaticEquilibriumOperator::FinalizeDisplacementJacobianPreparation() { const int dimension = m_fem.mesh->Dimension(); for (const ElementPAData &data : m_elements) { const int quadraturePointCount = data.quadratureWeights.Size(); MFEM_VERIFY( - data.baseDisplacementData.has_value() && - data.compactificationData.has_value() && - static_cast(data.baseMappingContexts.size()) == - quadraturePointCount && + data.baseDisplacementData.has_value() && data.compactificationData.has_value() && + static_cast(data.baseMappingContexts.size()) == quadraturePointCount && data.rotationGradient.Height() == quadraturePointCount && data.rotationGradient.Width() == dimension && data.hydrostaticImbalance.Size() == quadraturePointCount, @@ -864,9 +699,7 @@ namespace mean_field::operators { for (const ElementPAData &data : m_elements) { elementResidual.SetSize(data.enthalpyDofs.Size()); - data.enthalpyBasis.MultTranspose( - data.weightedResidual, elementResidual - ); + data.enthalpyBasis.MultTranspose(data.weightedResidual, elementResidual); if (data.enthalpyDofTransformation != nullptr) { data.enthalpyDofTransformation->TransformDual(elementResidual); @@ -878,9 +711,7 @@ namespace mean_field::operators { local_to_true(*m_fem.enthalpyFes, localResidual, m_cachedResidual); } - void PreparedHydrostaticEquilibriumOperator::BuildResidual( - mfem::Vector &residual - ) const { + void PreparedHydrostaticEquilibriumOperator::BuildResidual(mfem::Vector &residual) const { VerifyPrepared(); residual = m_cachedResidual; ++m_residualApplicationCount; @@ -894,9 +725,7 @@ namespace mean_field::operators { mfem::Vector enthalpyVariationLocal; - true_to_local( - *m_fem.enthalpyFes, enthalpyVariation, enthalpyVariationLocal - ); + true_to_local(*m_fem.enthalpyFes, enthalpyVariation, enthalpyVariationLocal); mfem::Vector localAction(m_fem.enthalpyFes->GetVSize()); @@ -906,14 +735,10 @@ namespace mean_field::operators { mfem::Vector elementAction; for (const ElementPAData &data : m_elements) { - enthalpyVariationLocal.GetSubVector( - data.enthalpyDofs, elementVariation - ); + enthalpyVariationLocal.GetSubVector(data.enthalpyDofs, elementVariation); if (data.enthalpyDofTransformation != nullptr) { - data.enthalpyDofTransformation->InvTransformPrimal( - elementVariation - ); + data.enthalpyDofTransformation->InvTransformPrimal(elementVariation); } elementAction.SetSize(data.enthalpyJacobian.Height()); @@ -932,8 +757,7 @@ namespace mean_field::operators { ++m_algebraicJacobianStatistics.enthalpyApplications; } - void - PreparedHydrostaticEquilibriumOperator::ApplyGravityPotentialJacobianAction( + void PreparedHydrostaticEquilibriumOperator::ApplyGravityPotentialJacobianAction( const mfem::Vector &gravityPotentialVariation, mfem::Vector &action ) const { @@ -941,10 +765,7 @@ namespace mean_field::operators { mfem::Vector gravityPotentialVariationLocal; - true_to_local( - *m_fem.gravityPotentialFes, gravityPotentialVariation, - gravityPotentialVariationLocal - ); + true_to_local(*m_fem.gravityPotentialFes, gravityPotentialVariation, gravityPotentialVariationLocal); mfem::Vector localAction(m_fem.enthalpyFes->GetVSize()); @@ -954,14 +775,10 @@ namespace mean_field::operators { mfem::Vector elementAction; for (const ElementPAData &data : m_elements) { - gravityPotentialVariationLocal.GetSubVector( - data.gravityPotentialDofs, elementVariation - ); + gravityPotentialVariationLocal.GetSubVector(data.gravityPotentialDofs, elementVariation); if (data.gravityPotentialDofTransformation != nullptr) { - data.gravityPotentialDofTransformation->InvTransformPrimal( - elementVariation - ); + data.gravityPotentialDofTransformation->InvTransformPrimal(elementVariation); } elementAction.SetSize(data.gravityPotentialJacobian.Height()); @@ -980,17 +797,15 @@ namespace mean_field::operators { ++m_algebraicJacobianStatistics.gravityPotentialApplications; } - void PreparedHydrostaticEquilibriumOperator:: - ApplyBernoulliConstantJacobianAction( - const double bernoulliConstantVariation, - mfem::Vector &action - ) const { + void PreparedHydrostaticEquilibriumOperator::ApplyBernoulliConstantJacobianAction( + const double bernoulliConstantVariation, + mfem::Vector &action + ) const { VerifyPrepared(); MFEM_VERIFY( - std::isfinite(bernoulliConstantVariation), - "Prepared hydrostatic Bernoulli-constant Jacobian " - "received a non-finite variation." + std::isfinite(bernoulliConstantVariation), "Prepared hydrostatic Bernoulli-constant Jacobian " + "received a non-finite variation." ); mfem::Vector localAction(m_fem.enthalpyFes->GetVSize()); @@ -1023,22 +838,16 @@ namespace mean_field::operators { VerifyPrepared(); MFEM_VERIFY( - std::isfinite(bernoulliConstantVariation), - "Prepared hydrostatic algebraic Jacobian received " - "a non-finite Bernoulli-constant variation." + std::isfinite(bernoulliConstantVariation), "Prepared hydrostatic algebraic Jacobian received " + "a non-finite Bernoulli-constant variation." ); mfem::Vector enthalpyVariationLocal; mfem::Vector gravityPotentialVariationLocal; - true_to_local( - *m_fem.enthalpyFes, enthalpyVariation, enthalpyVariationLocal - ); + true_to_local(*m_fem.enthalpyFes, enthalpyVariation, enthalpyVariationLocal); - true_to_local( - *m_fem.gravityPotentialFes, gravityPotentialVariation, - gravityPotentialVariationLocal - ); + true_to_local(*m_fem.gravityPotentialFes, gravityPotentialVariation, gravityPotentialVariationLocal); mfem::Vector localAction(m_fem.enthalpyFes->GetVSize()); @@ -1050,35 +859,23 @@ namespace mean_field::operators { mfem::Vector elementWorkspace; for (const ElementPAData &data : m_elements) { - enthalpyVariationLocal.GetSubVector( - data.enthalpyDofs, elementEnthalpyVariation - ); + enthalpyVariationLocal.GetSubVector(data.enthalpyDofs, elementEnthalpyVariation); - gravityPotentialVariationLocal.GetSubVector( - data.gravityPotentialDofs, elementGravityPotentialVariation - ); + gravityPotentialVariationLocal.GetSubVector(data.gravityPotentialDofs, elementGravityPotentialVariation); if (data.enthalpyDofTransformation != nullptr) { - data.enthalpyDofTransformation->InvTransformPrimal( - elementEnthalpyVariation - ); + data.enthalpyDofTransformation->InvTransformPrimal(elementEnthalpyVariation); } if (data.gravityPotentialDofTransformation != nullptr) { - data.gravityPotentialDofTransformation->InvTransformPrimal( - elementGravityPotentialVariation - ); + data.gravityPotentialDofTransformation->InvTransformPrimal(elementGravityPotentialVariation); } MFEM_VERIFY( - data.enthalpyJacobian.Height() == - data.gravityPotentialJacobian.Height() && - data.enthalpyJacobian.Width() == - elementEnthalpyVariation.Size() && - data.gravityPotentialJacobian.Width() == - elementGravityPotentialVariation.Size() && - data.bernoulliConstantJacobian.Size() == - data.enthalpyJacobian.Height(), + data.enthalpyJacobian.Height() == data.gravityPotentialJacobian.Height() && + data.enthalpyJacobian.Width() == elementEnthalpyVariation.Size() && + data.gravityPotentialJacobian.Width() == elementGravityPotentialVariation.Size() && + data.bernoulliConstantJacobian.Size() == data.enthalpyJacobian.Height(), "Prepared hydrostatic algebraic Jacobian has " "incompatible element dimensions." ); @@ -1089,14 +886,10 @@ namespace mean_field::operators { data.enthalpyJacobian.Mult(elementEnthalpyVariation, elementAction); - data.gravityPotentialJacobian.Mult( - elementGravityPotentialVariation, elementWorkspace - ); + data.gravityPotentialJacobian.Mult(elementGravityPotentialVariation, elementWorkspace); elementAction.Add(1.0, elementWorkspace); - elementAction.Add( - bernoulliConstantVariation, data.bernoulliConstantJacobian - ); + elementAction.Add(bernoulliConstantVariation, data.bernoulliConstantJacobian); if (data.enthalpyDofTransformation != nullptr) { data.enthalpyDofTransformation->TransformDual(elementAction); @@ -1110,8 +903,7 @@ namespace mean_field::operators { ++m_algebraicJacobianStatistics.combinedApplications; } - void - PreparedHydrostaticEquilibriumOperator::ApplyDisplacementJacobianAction( + void PreparedHydrostaticEquilibriumOperator::ApplyDisplacementJacobianAction( const mfem::Vector &displacementVariation, mfem::Vector &action ) const { @@ -1119,18 +911,13 @@ namespace mean_field::operators { mfem::Vector displacementVariationLocal; - true_to_local( - *m_fem.displacementFes, displacementVariation, - displacementVariationLocal - ); + true_to_local(*m_fem.displacementFes, displacementVariation, displacementVariationLocal); mfem::Vector localAction(m_fem.enthalpyFes->GetVSize()); localAction = 0.0; - mapping::DomainMapperStateless::Workspace workspace( - m_fem.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); mfem::Vector elementDisplacementVariation; mfem::Vector weightedQuadratureVariation; @@ -1138,50 +925,38 @@ namespace mean_field::operators { for (const ElementPAData &data : m_elements) { MFEM_VERIFY( - data.baseDisplacementData.has_value() && - data.compactificationData.has_value() && + data.baseDisplacementData.has_value() && data.compactificationData.has_value() && data.integrationRule != nullptr, "Prepared hydrostatic displacement Jacobian " "has invalid frozen element data." ); - mfem::ElementTransformation *transformation = - m_fem.mesh->GetElementTransformation(data.elementId); + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); MFEM_VERIFY( - transformation != nullptr, - "Prepared hydrostatic displacement Jacobian " - "received a null element transformation." + transformation != nullptr, "Prepared hydrostatic displacement Jacobian " + "received a null element transformation." ); - displacementVariationLocal.GetSubVector( - data.displacementDofs, elementDisplacementVariation - ); + displacementVariationLocal.GetSubVector(data.displacementDofs, elementDisplacementVariation); if (data.displacementDofTransformation != nullptr) { - data.displacementDofTransformation->InvTransformPrimal( - elementDisplacementVariation - ); + data.displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); } - const mfem::FiniteElement &displacementElement = - *m_fem.displacementFes->GetFE(data.elementId); + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); const mapping::ElementDisplacementData directionData = - mapping::ElementDisplacementDataFromElementVDofs( - displacementElement, elementDisplacementVariation - ); + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation); const mapping::ElementMappingData mappingData{ - .displacement = *data.baseDisplacementData, - .compactification = *data.compactificationData + .displacement = *data.baseDisplacementData, .compactification = *data.compactificationData }; const int quadraturePointCount = data.integrationRule->GetNPoints(); MFEM_VERIFY( - static_cast(data.baseMappingContexts.size()) == - quadraturePointCount && + static_cast(data.baseMappingContexts.size()) == quadraturePointCount && data.quadratureWeights.Size() == quadraturePointCount && data.hydrostaticImbalance.Size() == quadraturePointCount && data.rotationGradient.Height() == quadraturePointCount && @@ -1192,66 +967,49 @@ namespace mean_field::operators { weightedQuadratureVariation.SetSize(quadraturePointCount); - for (int quadraturePoint = 0; - quadraturePoint < quadraturePointCount; ++quadraturePoint) { - const mfem::IntegrationPoint &integrationPoint = - data.integrationRule->IntPoint(quadraturePoint); + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); transformation->SetIntPoint(&integrationPoint); mapping::VolumeMappingVariation variation; - const mapping::MappingStatus mappingStatus = - m_domainMapper.EvaluateVolumeVariation( - mappingData, directionData, *transformation, - integrationPoint, - data.baseMappingContexts[quadraturePoint], workspace, - variation - ); + const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation( + mappingData, directionData, *transformation, integrationPoint, + data.baseMappingContexts[quadraturePoint], workspace, variation + ); MFEM_VERIFY( mappingStatus == mapping::MappingStatus::valid, "Stateless mapping variation failed while " "applying the prepared hydrostatic " "displacement Jacobian. Element: " - << data.elementId - << ", attribute: " << transformation->Attribute - << ", quadrature point: " << quadraturePoint - << ", status: " << static_cast(mappingStatus) + << data.elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) ); double rotationPotentialVariation = 0.0; - for (int component = 0; component < m_fem.mesh->Dimension(); - ++component) { - rotationPotentialVariation += - data.rotationGradient(quadraturePoint, component) * - variation.mapping.physical_position_variation( - component - ); + for (int component = 0; component < m_fem.mesh->Dimension(); ++component) { + rotationPotentialVariation += data.rotationGradient(quadraturePoint, component) * + variation.mapping.physical_position_variation(component); } const double weightedVariation = - data.hydrostaticImbalance(quadraturePoint) * - variation.weight_variation - - data.quadratureWeights(quadraturePoint) * - rotationPotentialVariation; + data.hydrostaticImbalance(quadraturePoint) * variation.weight_variation - + data.quadratureWeights(quadraturePoint) * rotationPotentialVariation; MFEM_VERIFY( - std::isfinite(weightedVariation), - "Prepared hydrostatic displacement Jacobian " - "encountered a non-finite quadrature action." + std::isfinite(weightedVariation), "Prepared hydrostatic displacement Jacobian " + "encountered a non-finite quadrature action." ); - weightedQuadratureVariation(quadraturePoint) = - weightedVariation; + weightedQuadratureVariation(quadraturePoint) = weightedVariation; } elementAction.SetSize(data.enthalpyDofs.Size()); - data.enthalpyBasis.MultTranspose( - weightedQuadratureVariation, elementAction - ); + data.enthalpyBasis.MultTranspose(weightedQuadratureVariation, elementAction); if (data.enthalpyDofTransformation != nullptr) { data.enthalpyDofTransformation->TransformDual(elementAction); @@ -1276,19 +1034,13 @@ namespace mean_field::operators { mfem::Vector displacementAction; - ApplyAlgebraicJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, action - ); + ApplyAlgebraicJacobianAction(enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, action); - ApplyDisplacementJacobianAction( - displacementVariation, displacementAction - ); + ApplyDisplacementJacobianAction(displacementVariation, displacementAction); MFEM_VERIFY( - action.Size() == displacementAction.Size(), - "Prepared hydrostatic complete Jacobian produced " - "incompatible algebraic and displacement actions." + action.Size() == displacementAction.Size(), "Prepared hydrostatic complete Jacobian produced " + "incompatible algebraic and displacement actions." ); action += displacementAction; @@ -1300,46 +1052,38 @@ namespace mean_field::operators { } const context::hydrostatic::HydrostaticPreparationStatistics & - PreparedHydrostaticEquilibriumOperator:: - GetContextPreparationStatistics() const noexcept { + PreparedHydrostaticEquilibriumOperator::GetContextPreparationStatistics() const noexcept { return m_context.GetPreparationStatistics(); } - std::uint64_t PreparedHydrostaticEquilibriumOperator:: - GetResidualPreparationCount() const noexcept { + std::uint64_t PreparedHydrostaticEquilibriumOperator::GetResidualPreparationCount() const noexcept { return m_residualPreparationCount; } - std::uint64_t PreparedHydrostaticEquilibriumOperator:: - GetResidualApplicationCount() const noexcept { + std::uint64_t PreparedHydrostaticEquilibriumOperator::GetResidualApplicationCount() const noexcept { return m_residualApplicationCount; } const PreparedHydrostaticAlgebraicJacobianStatistics & - PreparedHydrostaticEquilibriumOperator:: - GetAlgebraicJacobianStatistics() const noexcept { + PreparedHydrostaticEquilibriumOperator::GetAlgebraicJacobianStatistics() const noexcept { return m_algebraicJacobianStatistics; } const PreparedHydrostaticDisplacementJacobianStatistics & - PreparedHydrostaticEquilibriumOperator:: - GetDisplacementJacobianStatistics() const noexcept { + PreparedHydrostaticEquilibriumOperator::GetDisplacementJacobianStatistics() const noexcept { return m_displacementJacobianStatistics; } const PreparedHydrostaticCompleteJacobianStatistics & - PreparedHydrostaticEquilibriumOperator:: - GetCompleteJacobianStatistics() const noexcept { + PreparedHydrostaticEquilibriumOperator::GetCompleteJacobianStatistics() const noexcept { return m_completeJacobianStatistics; } - std::size_t PreparedHydrostaticEquilibriumOperator:: - GetStellarElementCount() const noexcept { + std::size_t PreparedHydrostaticEquilibriumOperator::GetStellarElementCount() const noexcept { return m_elements.size(); } - const fem::FEM & - PreparedHydrostaticEquilibriumOperator::GetFEM() const noexcept { + const fem::FEM &PreparedHydrostaticEquilibriumOperator::GetFEM() const noexcept { return m_fem; } @@ -1350,11 +1094,10 @@ namespace mean_field::operators { ); } - PreparedHydrostaticEquilibriumJacobianOperator:: - PreparedHydrostaticEquilibriumJacobianOperator( - const fem::FEM &f, - const PreparedHydrostaticEquilibriumOperator &preparedOperator - ) + PreparedHydrostaticEquilibriumJacobianOperator::PreparedHydrostaticEquilibriumJacobianOperator( + const fem::FEM &f, + const PreparedHydrostaticEquilibriumOperator &preparedOperator + ) : mfem::Operator( f.enthalpyFes != nullptr ? f.enthalpyFes->GetTrueVSize() : 0, HydrostaticJacobianBlockLayout(f).GetTotalSize() @@ -1362,14 +1105,12 @@ namespace mean_field::operators { m_layout(f), m_preparedOperator(preparedOperator) { MFEM_VERIFY( - &m_preparedOperator.GetFEM() == &f, - "Prepared hydrostatic MFEM adapter and prepared " - "operator must use the same FEM object." + &m_preparedOperator.GetFEM() == &f, "Prepared hydrostatic MFEM adapter and prepared " + "operator must use the same FEM object." ); MFEM_VERIFY( - Height() == m_layout.GetResidualSize() && - Width() == m_layout.GetTotalSize(), + Height() == m_layout.GetResidualSize() && Width() == m_layout.GetTotalSize(), "Prepared hydrostatic MFEM adapter has " "inconsistent operator dimensions." ); @@ -1380,9 +1121,8 @@ namespace mean_field::operators { mfem::Vector &action ) const { MFEM_VERIFY( - direction.Size() == Width(), - "Prepared hydrostatic MFEM adapter received a " - "direction with the wrong size." + direction.Size() == Width(), "Prepared hydrostatic MFEM adapter received a " + "direction with the wrong size." ); mfem::Vector enthalpyVariation; @@ -1391,42 +1131,33 @@ namespace mean_field::operators { copy_vector_block( direction, m_layout.Offset(HydrostaticJacobianInputBlock::enthalpy), - m_layout.Size(HydrostaticJacobianInputBlock::enthalpy), - enthalpyVariation + m_layout.Size(HydrostaticJacobianInputBlock::enthalpy), enthalpyVariation ); copy_vector_block( - direction, - m_layout.Offset(HydrostaticJacobianInputBlock::gravityPotential), - m_layout.Size(HydrostaticJacobianInputBlock::gravityPotential), - gravityPotentialVariation + direction, m_layout.Offset(HydrostaticJacobianInputBlock::gravityPotential), + m_layout.Size(HydrostaticJacobianInputBlock::gravityPotential), gravityPotentialVariation ); copy_vector_block( - direction, - m_layout.Offset(HydrostaticJacobianInputBlock::displacement), - m_layout.Size(HydrostaticJacobianInputBlock::displacement), - displacementVariation + direction, m_layout.Offset(HydrostaticJacobianInputBlock::displacement), + m_layout.Size(HydrostaticJacobianInputBlock::displacement), displacementVariation ); - const double bernoulliConstantVariation = direction( - m_layout.Offset(HydrostaticJacobianInputBlock::bernoulliConstant) - ); + const double bernoulliConstantVariation = + direction(m_layout.Offset(HydrostaticJacobianInputBlock::bernoulliConstant)); m_preparedOperator.ApplyCompleteJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, displacementVariation, action + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, displacementVariation, action ); MFEM_VERIFY( - action.Size() == Height(), - "Prepared hydrostatic MFEM adapter produced an " - "action with the wrong size." + action.Size() == Height(), "Prepared hydrostatic MFEM adapter produced an " + "action with the wrong size." ); } - const HydrostaticJacobianBlockLayout & - PreparedHydrostaticEquilibriumJacobianOperator::GetLayout() const noexcept { + const HydrostaticJacobianBlockLayout &PreparedHydrostaticEquilibriumJacobianOperator::GetLayout() const noexcept { return m_layout; } } // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/prepared_mass_normalization.cpp b/libmeanfield/impl/operators/prepared_mass_normalization.cpp new file mode 100644 index 0000000..856ec7e --- /dev/null +++ b/libmeanfield/impl/operators/prepared_mass_normalization.cpp @@ -0,0 +1,668 @@ +module; + +#include +#include +#include + +module mean_field; + +import :operators.prepared_mass_normalization; + +namespace { + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + void true_to_local( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &trueVector, + mfem::Vector &localVector + ) { + MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size."); + + localVector.SetSize(finiteElementSpace.GetVSize()); + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->Mult(trueVector, localVector); + } else { + localVector = trueVector; + } + } + + const mfem::IntegrationRule &get_mass_normalization_rule( + const mean_field::fem::FEM &f, + const mfem::FiniteElement &densityElement, + const mfem::ElementTransformation &transformation + ) { + using DensityField = mean_field::field::Field; + + MFEM_VERIFY( + densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder, + "The mass-normalization element does not match the registered " + "density field." + ); + + const mean_field::quadrature::Query query = + DensityField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array{}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); + + const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType()); + + MFEM_VERIFY( + resolution.integration_rule != nullptr, "The quadrature policy did not return a mass-normalization rule." + ); + + return *resolution.integration_rule; + } + + void validate_shared_gravity_revisions( + const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext, + const mean_field::operators::MassNormalizationDependencies &dependencies + ) { + MFEM_VERIFY( + gravityContext.IsPrepared(), "PreparedMassNormalizationOperator requires the shared gravity " + "linearization context to be prepared first." + ); + + const auto &revisions = gravityContext.GetRevisions(); + + MFEM_VERIFY( + revisions.discretization.value == dependencies.discretization.revision && + revisions.density.value == dependencies.density.revision && + revisions.displacement.value == dependencies.displacement.revision, + "PreparedMassNormalizationOperator received dependency revisions " + "that do not match the shared gravity context." + ); + } + + void validate_shared_identity_transition( + const mean_field::operators::MassNormalizationDependencyStamp &prepared, + const mean_field::operators::MassNormalizationDependencyStamp &requested, + const char *message + ) { + MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message); + } +} // namespace + +namespace mean_field::operators { + PreparedMassNormalizationOperator::PreparedMassNormalizationOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ) + : m_fem(f), + m_domainMapper(domainMapper), + m_gravityContext(gravityContext) { + MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedMassNormalizationOperator requires a mesh."); + MFEM_VERIFY( + m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr && m_fem.compactificationFes != nullptr && + m_fem.compactificationCoordinate != nullptr && m_fem.quadratureFactory != nullptr, + "PreparedMassNormalizationOperator requires density, " + "displacement, compactification, and quadrature data." + ); + MFEM_VERIFY( + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), + "PreparedMassNormalizationOperator received a mapper with the " + "wrong dimension." + ); + } + + PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare( + const MassNormalizationStateView &state, + const MassNormalizationDependencies &dependencies + ) { + MFEM_VERIFY( + std::isfinite(state.targetMass) && state.targetMass > 0.0, + "PreparedMassNormalizationOperator requires a finite, positive " + "target mass." + ); + + validate_shared_gravity_revisions(m_gravityContext, dependencies); + + if (m_isPrepared) { + validate_shared_identity_transition( + m_preparedDependencies.discretization, dependencies.discretization, + "A new mass-normalization discretization identity must also " + "change the shared gravity revision." + ); + validate_shared_identity_transition( + m_preparedDependencies.density, dependencies.density, + "A new mass-normalization density identity must also change " + "the shared gravity revision." + ); + validate_shared_identity_transition( + m_preparedDependencies.displacement, dependencies.displacement, + "A new mass-normalization displacement identity must also " + "change the shared gravity revision." + ); + } + + const bool rebuildStaticPlan = + !m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization; + + const bool refreshGeometry = + rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement; + + const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density; + + const bool updateTargetMass = !m_isPrepared || dependencies.targetMass != m_preparedDependencies.targetMass || + state.targetMass != m_targetMass; + + m_isPrepared = false; + + PreparedMassNormalizationReport report; + + if (rebuildStaticPlan) { + BuildStaticPlan(); + report.rebuiltStaticPlan = true; + } + + if (refreshGeometry) { + RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacement()); + report.refreshedGeometry = true; + } + + if (refreshDensity) { + RefreshDensity(m_gravityContext.GetDensity()); + report.refreshedDensity = true; + } + + if (updateTargetMass) { + m_targetMass = state.targetMass; + report.updatedTargetMass = true; + } + + if (refreshGeometry || refreshDensity) { + AssembleResidual(); + report.assembledResidual = true; + } else if (updateTargetMass) { + m_cachedResidual.SetSize(1); + m_cachedResidual(0) = m_currentMass - m_targetMass; + ++m_preparationCount; + report.assembledResidual = true; + } + + m_preparedDependencies = dependencies; + m_isPrepared = true; + return report; + } + + void PreparedMassNormalizationOperator::BuildStaticPlan() { + m_elements.clear(); + m_elements.reserve(m_fem.mesh->GetNE()); + + const int vacuumAttribute = m_domainMapper.GetVacuumElementAttribute(); + + int localStellarElementCount = 0; + + for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId); + + MFEM_VERIFY( + transformation != nullptr, "PreparedMassNormalizationOperator received a null element " + "transformation." + ); + + if (transformation->Attribute == vacuumAttribute) { + continue; + } + + ++localStellarElementCount; + m_elements.emplace_back(); + ElementPAData &data = m_elements.back(); + data.elementId = elementId; + + data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs); + + data.displacementDofTransformation = + m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs); + + data.compactificationDofTransformation = + m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs); + + const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId); + + const mfem::IntegrationRule &integrationRule = + get_mass_normalization_rule(m_fem, densityElement, *transformation); + + data.quadraturePoints.resize(integrationRule.GetNPoints()); + + for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) { + QuadraturePointData &point = data.quadraturePoints[quadraturePoint]; + + point.integrationPoint = integrationRule.IntPoint(quadraturePoint); + + point.densityShape.SetSize(densityElement.GetDof()); + densityElement.CalcShape(point.integrationPoint, point.densityShape); + } + } + + int globalStellarElementCount = 0; + MPI_Allreduce( + &localStellarElementCount, &globalStellarElementCount, 1, MPI_INT, MPI_SUM, m_fem.mesh->GetComm() + ); + + MFEM_VERIFY(globalStellarElementCount > 0, "PreparedMassNormalizationOperator found no stellar elements."); + } + + void PreparedMassNormalizationOperator::RefreshGeometry(const mfem::Vector &displacement) { + MFEM_VERIFY( + displacement.Size() == m_fem.displacementFes->GetTrueVSize(), + "PreparedMassNormalizationOperator received a displacement " + "vector with the wrong size." + ); + validate_finite_vector( + displacement, "PreparedMassNormalizationOperator received a non-finite " + "displacement value." + ); + + mfem::Vector displacementLocal; + true_to_local(*m_fem.displacementFes, displacement, displacementLocal); + + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); + + for (ElementPAData &data : m_elements) { + displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement); + + m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, data.compactification); + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->InvTransformPrimal(data.baseDisplacement); + } + + if (data.compactificationDofTransformation != nullptr) { + data.compactificationDofTransformation->InvTransformPrimal(data.compactification); + } + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId); + + const mapping::ElementDisplacementData displacementData = + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement); + + const mapping::ElementCompactificationData compactificationData( + compactificationElement, data.compactification + ); + + const mapping::ElementMappingData mappingData{ + .displacement = displacementData, .compactification = compactificationData + }; + + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); + + for (QuadraturePointData &point : data.quadraturePoints) { + const mapping::MappingStatus status = m_domainMapper.EvaluateVolume( + mappingData, *transformation, point.integrationPoint, workspace, point.mappingContext + ); + + MFEM_VERIFY( + status == mapping::MappingStatus::valid, "Stateless mapping failed while preparing mass " + "normalization. Element: " + << data.elementId + << ", attribute: " << transformation->Attribute + << ", status: " << static_cast(status) + ); + } + } + } + + void PreparedMassNormalizationOperator::RefreshDensity(const mfem::Vector &density) { + MFEM_VERIFY( + density.Size() == m_fem.densityFes->GetTrueVSize(), + "PreparedMassNormalizationOperator received a density vector " + "with the wrong size." + ); + validate_finite_vector( + density, "PreparedMassNormalizationOperator received a non-finite density " + "value." + ); + + mfem::Vector densityLocal; + true_to_local(*m_fem.densityFes, density, densityLocal); + + mfem::Vector elementDensity; + + for (ElementPAData &data : m_elements) { + densityLocal.GetSubVector(data.densityDofs, elementDensity); + + if (data.densityDofTransformation != nullptr) { + data.densityDofTransformation->InvTransformPrimal(elementDensity); + } + + for (QuadraturePointData &point : data.quadraturePoints) { + point.density = elementDensity * point.densityShape; + MFEM_VERIFY( + std::isfinite(point.density), "PreparedMassNormalizationOperator produced a non-finite " + "quadrature density." + ); + } + } + } + + void PreparedMassNormalizationOperator::AssembleResidual() { + double localMass = 0.0; + + for (const ElementPAData &data : m_elements) { + for (const QuadraturePointData &point : data.quadraturePoints) { + localMass += point.density * point.mappingContext.quadrature.weight; + } + } + + m_currentMass = GlobalSum(localMass); + MFEM_VERIFY(std::isfinite(m_currentMass), "PreparedMassNormalizationOperator assembled a non-finite mass."); + + m_cachedResidual.SetSize(1); + m_cachedResidual(0) = m_currentMass - m_targetMass; + ++m_preparationCount; + } + + void PreparedMassNormalizationOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + residual = m_cachedResidual; + ++m_residualApplicationCount; + } + + double PreparedMassNormalizationOperator::EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const { + MFEM_VERIFY( + densityVariation.Size() == m_fem.densityFes->GetTrueVSize(), + "Mass-normalization density action received a vector with the " + "wrong size." + ); + validate_finite_vector(densityVariation, "Mass-normalization density action received a non-finite value."); + + mfem::Vector densityVariationLocal; + true_to_local(*m_fem.densityFes, densityVariation, densityVariationLocal); + + mfem::Vector elementDensityVariation; + double localAction = 0.0; + + for (const ElementPAData &data : m_elements) { + densityVariationLocal.GetSubVector(data.densityDofs, elementDensityVariation); + + if (data.densityDofTransformation != nullptr) { + data.densityDofTransformation->InvTransformPrimal(elementDensityVariation); + } + + for (const QuadraturePointData &point : data.quadraturePoints) { + localAction += (elementDensityVariation * point.densityShape) * point.mappingContext.quadrature.weight; + } + } + + return localAction; + } + + double PreparedMassNormalizationOperator::EvaluateDisplacementActionLocal( + const mfem::Vector &displacementVariation + ) const { + MFEM_VERIFY( + displacementVariation.Size() == m_fem.displacementFes->GetTrueVSize(), + "Mass-normalization displacement action received a vector with " + "the wrong size." + ); + validate_finite_vector( + displacementVariation, "Mass-normalization displacement action received a non-finite " + "value." + ); + + mfem::Vector displacementVariationLocal; + true_to_local(*m_fem.displacementFes, displacementVariation, displacementVariationLocal); + + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); + + mfem::Vector elementDisplacementVariation; + double localAction = 0.0; + + for (const ElementPAData &data : m_elements) { + displacementVariationLocal.GetSubVector(data.displacementDofs, elementDisplacementVariation); + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); + } + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId); + + const mapping::ElementDisplacementData baseDisplacementData = + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement); + + const mapping::ElementDisplacementData directionData = + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation); + + const mapping::ElementCompactificationData compactificationData( + compactificationElement, data.compactification + ); + + const mapping::ElementMappingData mappingData{ + .displacement = baseDisplacementData, .compactification = compactificationData + }; + + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); + + for (const QuadraturePointData &point : data.quadraturePoints) { + mapping::VolumeMappingVariation variation; + + const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation( + mappingData, directionData, *transformation, point.integrationPoint, point.mappingContext, + workspace, variation + ); + + MFEM_VERIFY( + status == mapping::MappingStatus::valid, "Stateless mapping variation failed in the " + "mass-normalization displacement action. Element: " + << data.elementId + << ", status: " << static_cast(status) + ); + + localAction += point.density * variation.weight_variation; + } + } + + return localAction; + } + + void PreparedMassNormalizationOperator::ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + action.SetSize(1); + action(0) = GlobalSum(EvaluateDensityActionLocal(densityVariation)); + ++m_actionStatistics.densityApplications; + } + + void PreparedMassNormalizationOperator::ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + action.SetSize(1); + action(0) = GlobalSum(EvaluateDisplacementActionLocal(displacementVariation)); + ++m_actionStatistics.displacementApplications; + } + + void PreparedMassNormalizationOperator::ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + const double localAction = + EvaluateDensityActionLocal(densityVariation) + EvaluateDisplacementActionLocal(displacementVariation); + + action.SetSize(1); + action(0) = GlobalSum(localAction); + ++m_actionStatistics.completeApplications; + } + + double PreparedMassNormalizationOperator::GlobalSum(const double localValue) const { + double globalValue = 0.0; + MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm()); + return globalValue; + } + + bool PreparedMassNormalizationOperator::IsPrepared() const noexcept { + if (!m_isPrepared || !m_gravityContext.IsPrepared()) { + return false; + } + + const auto &revisions = m_gravityContext.GetRevisions(); + return revisions.discretization.value == m_preparedDependencies.discretization.revision && + revisions.density.value == m_preparedDependencies.density.revision && + revisions.displacement.value == m_preparedDependencies.displacement.revision; + } + + double PreparedMassNormalizationOperator::GetCurrentMass() const { + VerifyPrepared(); + return m_currentMass; + } + + double PreparedMassNormalizationOperator::GetTargetMass() const { + VerifyPrepared(); + return m_targetMass; + } + + std::uint64_t PreparedMassNormalizationOperator::GetPreparationCount() const noexcept { + return m_preparationCount; + } + + std::uint64_t PreparedMassNormalizationOperator::GetResidualApplicationCount() const noexcept { + return m_residualApplicationCount; + } + + const PreparedMassNormalizationActionStatistics & + PreparedMassNormalizationOperator::GetActionStatistics() const noexcept { + return m_actionStatistics; + } + + const fem::FEM &PreparedMassNormalizationOperator::GetFEM() const noexcept { + return m_fem; + } + + const context::gravity_field::GravityFieldLinearizationContext & + PreparedMassNormalizationOperator::GetGravityContext() const noexcept { + return m_gravityContext; + } + + void PreparedMassNormalizationOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedMassNormalizationOperator must be prepared for the " + "current shared gravity-context revisions." + ); + } + + PreparedMassNormalizationJacobianOperator::PreparedMassNormalizationJacobianOperator( + const MassNormalizationLayout &layout, + const PreparedMassNormalizationOperator &preparedOperator + ) + : mfem::Operator( + layout.residual_offsets().Last(), + layout.value_offsets().Last() + ), + m_layout(layout), + m_preparedOperator(preparedOperator) { + const fem::FEM &f = m_preparedOperator.GetFEM(); + + MFEM_VERIFY( + f.densityFes != nullptr && f.displacementFes != nullptr && f.gravityFluxFes != nullptr && + f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr, + "Prepared mass-normalization MFEM adapter requires every " + "finite-element space in the barotropic equilibrium layout." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto barotropicConstantValue = + utils::blocks::get_value_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + constexpr auto gravityGradientResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); + constexpr auto densityResidual = + utils::blocks::get_residual_block(utils::blocks::density_field.mass_term); + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + constexpr auto enthalpyResidual = + utils::blocks::get_residual_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto massResidual = + utils::blocks::get_residual_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + MFEM_VERIFY( + m_layout.size(densityValue) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementValue) == f.displacementFes->GetTrueVSize() && + m_layout.size(gravityGradientValue) == f.gravityFluxFes->GetTrueVSize() && + m_layout.size(gravityPotentialValue) == f.gravityPotentialFes->GetTrueVSize() && + m_layout.size(enthalpyValue) == f.enthalpyFes->GetTrueVSize() && + m_layout.size(barotropicConstantValue) == 1 && + m_layout.size(gravityGradientResidual) == f.gravityFluxFes->GetTrueVSize() && + m_layout.size(gravityPotentialResidual) == f.gravityPotentialFes->GetTrueVSize() && + m_layout.size(densityResidual) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementResidual) == f.displacementFes->GetTrueVSize() && + m_layout.size(enthalpyResidual) == f.enthalpyFes->GetTrueVSize() && m_layout.size(massResidual) == 1, + "Prepared mass-normalization MFEM adapter received incompatible " + "barotropic block sizes." + ); + } + + void PreparedMassNormalizationJacobianOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + MFEM_VERIFY( + m_preparedOperator.IsPrepared(), "Prepared mass-normalization MFEM adapter requires a prepared " + "row operator." + ); + MFEM_VERIFY( + direction.Size() == Width(), "Prepared mass-normalization MFEM adapter received a direction " + "with the wrong size." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + constexpr auto massResidual = + utils::blocks::get_residual_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + const mfem::Vector densityVariation( + const_cast(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue) + ); + + const mfem::Vector displacementVariation( + const_cast(direction.GetData()) + m_layout.offset(displacementValue), + m_layout.size(displacementValue) + ); + + mfem::Vector massAction; + m_preparedOperator.ApplyCompleteJacobianAction(densityVariation, displacementVariation, massAction); + + action.SetSize(Height()); + action = 0.0; + action(m_layout.offset(massResidual)) = massAction(0); + } + + const MassNormalizationLayout &PreparedMassNormalizationJacobianOperator::GetLayout() const noexcept { + return m_layout; + } +} // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/impl/operators/prepared_pressure_force.cpp b/libmeanfield/impl/operators/prepared_pressure_force.cpp new file mode 100644 index 0000000..830de8c --- /dev/null +++ b/libmeanfield/impl/operators/prepared_pressure_force.cpp @@ -0,0 +1,1137 @@ +module; + +#include +#include +#include +#include +#include + +#include + +module mean_field; + +import :operators.prepared_pressure_force; +import :field.registry; +import :utils.blocks; +import :utils.domain; + +namespace { + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + using PressureDomain = mean_field::field::FieldDomainT; + + void verify_required_spaces(const mean_field::fem::FEM &f) { + MFEM_VERIFY(f.mesh != nullptr, "PreparedPressureForceOperator requires a mesh."); + + MFEM_VERIFY( + f.enthalpyFes != nullptr, "PreparedPressureForceOperator requires the enthalpy " + "finite-element space." + ); + + MFEM_VERIFY( + f.displacementFes != nullptr, "PreparedPressureForceOperator requires the displacement " + "finite-element space." + ); + + MFEM_VERIFY( + f.compactificationFes != nullptr, "PreparedPressureForceOperator requires the compactification " + "finite-element space." + ); + + MFEM_VERIFY( + f.compactificationCoordinate != nullptr, "PreparedPressureForceOperator requires the compactification " + "coordinate." + ); + + MFEM_VERIFY( + f.quadratureFactory != nullptr, "PreparedPressureForceOperator requires the quadrature-rule " + "factory." + ); + } + + [[nodiscard]] + bool element_is_in_pressure_support(const int attribute) { + return DomainSchema::template attribute_belongs_to(attribute); + } + + void true_to_local( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &trueVector, + mfem::Vector &localVector + ) { + MFEM_VERIFY( + trueVector.Size() == finiteElementSpace.GetTrueVSize(), + "Prepared pressure-force true vector has the wrong size." + ); + + localVector.SetSize(finiteElementSpace.GetVSize()); + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->Mult(trueVector, localVector); + } else { + localVector = trueVector; + } + } + + void local_to_true( + const mfem::ParFiniteElementSpace &finiteElementSpace, + const mfem::Vector &localVector, + mfem::Vector &trueVector + ) { + MFEM_VERIFY( + localVector.Size() == finiteElementSpace.GetVSize(), + "Prepared pressure-force local vector has the wrong size." + ); + + trueVector.SetSize(finiteElementSpace.GetTrueVSize()); + + trueVector = 0.0; + + const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix(); + + if (prolongation != nullptr) { + prolongation->MultTranspose(localVector, trueVector); + } else { + trueVector = localVector; + } + } + + [[nodiscard]] + int vector_dof_index( + const mfem::Ordering::Type ordering, + const int scalarDof, + const int component, + const int scalarDofCount, + const int dimension + ) { + if (ordering == mfem::Ordering::byNODES) { + return scalarDof + component * scalarDofCount; + } + + if (ordering == mfem::Ordering::byVDIM) { + return scalarDof * dimension + component; + } + + MFEM_ABORT( + "The prepared pressure-force displacement space uses an " + "unsupported ordering." + ); + + return -1; + } + + [[nodiscard]] + int get_pressure_extra_order(const mean_field::eos::Polytrope &equationOfState) { + const double extraOrder = + equationOfState.polytropic_index() * static_cast(mean_field::field::Enthalpy::Scalar::familyOrder); + + MFEM_VERIFY( + std::isfinite(extraOrder) && extraOrder >= 0.0 && + extraOrder <= static_cast(std::numeric_limits::max()), + "The prepared pressure-force EOS effective polynomial order " + "is invalid." + ); + + return static_cast(std::ceil(extraOrder)); + } + + [[nodiscard]] + const mfem::IntegrationRule &get_pressure_force_rule( + const mean_field::fem::FEM &f, + const mean_field::eos::Polytrope &equationOfState, + const mfem::FiniteElement &enthalpyElement, + const mfem::FiniteElement &displacementElement, + const mfem::ElementTransformation &transformation + ) { + using EnthalpyField = mean_field::field::Field; + + MFEM_VERIFY( + enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder, + "The prepared pressure-force enthalpy element does not " + "match the registered enthalpy field." + ); + + MFEM_VERIFY( + displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder, + "The prepared pressure-force test element does not match " + "the registered displacement field." + ); + + /* + * Query.domain remains legacy quadrature metadata for now. + * + * Physical element selection is no longer based on utils::DOMAINS; + * it is performed from Enthalpy::Support + DomainSchema in + * PrepareStaticPlan(). + */ + const mean_field::quadrature::Query query = + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), + std::array{get_pressure_extra_order(equationOfState)}, mean_field::utils::DOMAINS::STELLAR, + mean_field::quadrature::MappingKind::general + ); + + const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType()); + + MFEM_VERIFY( + rule.integration_rule != nullptr, "The quadrature policy did not return a prepared " + "pressure-force integration rule." + ); + + return *rule.integration_rule; + } +} // namespace + +namespace mean_field::operators { + struct PreparedPressureForceOperator::ConstructionData final { + field::FieldDofMap enthalpyMap; + + field::FieldDofMap displacementMap; + + explicit ConstructionData(const fem::FEM &f) + : enthalpyMap( + field::make_field_dof_map< + field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ), + displacementMap( + field::make_field_dof_map< + field::Displacement, + DomainSchema>(*f.displacementFes) + ) { + } + }; + + PreparedPressureForceOperator::ConstructionData + PreparedPressureForceOperator::MakeConstructionData(const fem::FEM &f) { + verify_required_spaces(f); + + return ConstructionData(f); + } + + PreparedPressureForceOperator::PreparedPressureForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState + ) + : PreparedPressureForceOperator( + f, + domainMapper, + equationOfState, + MakeConstructionData(f) + ) { + } + + PreparedPressureForceOperator::PreparedPressureForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + ConstructionData constructionData + ) + : m_fem(f), + m_domainMapper(domainMapper), + m_equationOfState(equationOfState), + m_enthalpyMap(std::move(constructionData.enthalpyMap)), + m_displacementMap(std::move(constructionData.displacementMap)), + m_context( + f, + domainMapper, + m_enthalpyMap, + m_displacementMap + ) { + MFEM_VERIFY( + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), + "The prepared pressure-force mapper dimension does not " + "match the mesh dimension." + ); + + MFEM_VERIFY( + m_fem.displacementFes->GetVDim() == m_fem.mesh->Dimension(), + "The prepared pressure-force displacement dimension does " + "not match the mesh dimension." + ); + + MFEM_VERIFY( + m_fem.displacementFes->GetOrdering() == mfem::Ordering::byNODES, + "PreparedPressureForceOperator requires the registered " + "byNODES displacement ordering." + ); + + MFEM_VERIFY( + m_enthalpyMap.full_size() == m_fem.enthalpyFes->GetTrueVSize(), + "The pressure-force enthalpy FieldDofMap does not match " + "the enthalpy finite-element space." + ); + + MFEM_VERIFY( + m_displacementMap.full_size() == m_fem.displacementFes->GetTrueVSize(), + "The pressure-force displacement FieldDofMap does not " + "match the displacement finite-element space." + ); + + m_baseEnthalpyTrue.SetSize(m_enthalpyMap.full_size()); + + m_baseDisplacementTrue.SetSize(m_displacementMap.full_size()); + + m_enthalpyVariationTrue.SetSize(m_enthalpyMap.full_size()); + + m_displacementVariationTrue.SetSize(m_displacementMap.full_size()); + + m_fullDisplacementAction.SetSize(m_displacementMap.full_size()); + + m_baseEnthalpyTrue = 0.0; + + m_baseDisplacementTrue = 0.0; + + m_enthalpyVariationTrue = 0.0; + + m_displacementVariationTrue = 0.0; + + m_fullDisplacementAction = 0.0; + } + + PreparedPressureForceReport PreparedPressureForceOperator::Prepare( + const context::pressure_force::PressureForceStateView &state, + const context::pressure_force::PressureForceDependencies &dependencies + ) { + PreparedPressureForceReport report; + + report.contextReport = m_context.Prepare(state, dependencies); + + if (!report.contextReport.DidAnyWork() && m_isPrepared) { + return report; + } + + /* + * Canonical FieldDof -> MFEM expansion. + * + * Unsupported enthalpy true DOFs are set exactly to zero. + * Displacement currently has an identity map but is intentionally + * routed through the same abstraction. + */ + m_enthalpyMap.scatter(m_context.GetBaseEnthalpy(), m_baseEnthalpyTrue); + + m_displacementMap.scatter(m_context.GetDisplacement(), m_baseDisplacementTrue); + + m_isPrepared = false; + + if (report.contextReport.preparedStaticDependencies) { + PrepareStaticPlan(); + } + + if (report.contextReport.preparedGeometryState) { + PrepareGeometry(); + } + + if (report.contextReport.preparedMaterialState) { + PrepareMaterialState(); + + FinalizeDisplacementJacobianPreparation(); + + AssembleCachedResidual(); + + ++m_residualPreparationCount; + + report.preparedEnthalpyJacobianData = true; + + report.preparedDisplacementJacobianData = true; + + report.preparedResidual = true; + } + + MFEM_VERIFY( + !m_elements.empty(), "PreparedPressureForceOperator found no elements in the " + "pressure-force field support." + ); + + MFEM_VERIFY( + m_cachedResidual.Size() == m_displacementMap.reduced_size(), + "The prepared pressure-force residual has the wrong " + "supported displacement size." + ); + + m_isPrepared = true; + + return report; + } + + void PreparedPressureForceOperator::PrepareStaticPlan() { + m_elements.clear(); + + m_elements.reserve(m_fem.mesh->GetNE()); + + mfem::Vector enthalpyShape; + mfem::DenseMatrix displacementDShape; + + for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId); + + MFEM_VERIFY( + transformation != nullptr, "Prepared pressure-force static planning received a null " + "element transformation." + ); + + if (!element_is_in_pressure_support(transformation->Attribute)) { + continue; + } + + const mfem::FiniteElement &enthalpyElement = *m_fem.enthalpyFes->GetFE(elementId); + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId); + + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId); + + MFEM_VERIFY( + enthalpyElement.GetGeomType() == displacementElement.GetGeomType() && + enthalpyElement.GetGeomType() == compactificationElement.GetGeomType() && + enthalpyElement.GetGeomType() == transformation->GetGeometryType(), + "Prepared pressure-force element geometries do not agree." + ); + + m_elements.emplace_back(); + + ElementPAData &data = m_elements.back(); + + data.elementId = elementId; + + data.enthalpyDofTransformation = m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs); + + data.displacementDofTransformation = + m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs); + + data.compactificationDofTransformation = + m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs); + + data.integrationRule = &get_pressure_force_rule( + m_fem, m_equationOfState, enthalpyElement, displacementElement, *transformation + ); + + const int dimension = m_fem.mesh->Dimension(); + + const int quadraturePointCount = data.integrationRule->GetNPoints(); + + const int enthalpyDofCount = enthalpyElement.GetDof(); + + const int scalarDisplacementDofCount = displacementElement.GetDof(); + + MFEM_VERIFY(quadraturePointCount > 0, "The prepared pressure-force integration rule is empty."); + + MFEM_VERIFY( + data.enthalpyDofs.Size() == enthalpyDofCount, "The prepared pressure-force enthalpy element has an " + "unexpected DOF count." + ); + + MFEM_VERIFY( + data.displacementDofs.Size() == scalarDisplacementDofCount * dimension, + "The prepared pressure-force displacement element has " + "an unexpected vector DOF count." + ); + + data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount); + + data.referenceTestGradients.resize(quadraturePointCount); + + data.physicalTestGradients.resize(quadraturePointCount); + + enthalpyShape.SetSize(enthalpyDofCount); + + displacementDShape.SetSize(scalarDisplacementDofCount, dimension); + + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); + + enthalpyElement.CalcShape(integrationPoint, enthalpyShape); + + displacementElement.CalcDShape(integrationPoint, displacementDShape); + + for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) { + data.enthalpyBasis(quadraturePoint, enthalpyDof) = enthalpyShape(enthalpyDof); + } + + data.referenceTestGradients[quadraturePoint] = displacementDShape; + } + } + } + + void PreparedPressureForceOperator::PrepareGeometry() { + mfem::Vector displacementLocal; + + true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal); + + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); + + mfem::Vector elementDisplacement; + mfem::Vector elementCompactification; + + for (ElementPAData &data : m_elements) { + MFEM_VERIFY(data.integrationRule != nullptr, "Prepared pressure-force geometry has no integration rule."); + + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); + + MFEM_VERIFY( + transformation != nullptr, "Prepared pressure-force geometry received a null " + "element transformation." + ); + + displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement); + + m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, elementCompactification); + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->InvTransformPrimal(elementDisplacement); + } + + if (data.compactificationDofTransformation != nullptr) { + data.compactificationDofTransformation->InvTransformPrimal(elementCompactification); + } + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + + const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId); + + data.baseDisplacementData.emplace( + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement) + ); + + data.compactificationData.emplace(compactificationElement, elementCompactification); + + const mapping::ElementMappingData mappingData{ + .displacement = *data.baseDisplacementData, .compactification = *data.compactificationData + }; + + const int quadraturePointCount = data.integrationRule->GetNPoints(); + + MFEM_VERIFY( + static_cast(data.referenceTestGradients.size()) == quadraturePointCount, + "Prepared pressure-force geometry has inconsistent " + "static gradient data." + ); + + data.quadratureWeights.SetSize(quadraturePointCount); + + data.baseMappingContexts.resize(quadraturePointCount); + + data.physicalTestGradients.resize(quadraturePointCount); + + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); + + transformation->SetIntPoint(&integrationPoint); + + mapping::VolumeMappingContext &mappingContext = data.baseMappingContexts[quadraturePoint]; + + const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); + + MFEM_VERIFY( + mappingStatus == mapping::MappingStatus::valid, + "Stateless mapping failed while preparing " + "pressure-force geometry. Element: " + << data.elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) + ); + + const double quadratureWeight = mappingContext.quadrature.weight; + + MFEM_VERIFY( + std::isfinite(quadratureWeight) && quadratureWeight > 0.0, + "Prepared pressure-force geometry encountered an " + "invalid quadrature weight." + ); + + data.quadratureWeights(quadraturePoint) = quadratureWeight; + + const mfem::DenseMatrix &referenceTestGradient = data.referenceTestGradients[quadraturePoint]; + + mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint]; + + MFEM_VERIFY( + referenceTestGradient.Width() == mappingContext.quadrature.J_inv.Height() && + mappingContext.quadrature.J_inv.Width() == m_fem.mesh->Dimension(), + "Prepared pressure-force geometry encountered " + "incompatible test-gradient and inverse-Jacobian " + "dimensions." + ); + + physicalTestGradient.SetSize(referenceTestGradient.Height(), mappingContext.quadrature.J_inv.Width()); + + mfem::Mult(referenceTestGradient, mappingContext.quadrature.J_inv, physicalTestGradient); + } + } + } + + void PreparedPressureForceOperator::PrepareMaterialState() { + mfem::Vector enthalpyLocal; + + true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, enthalpyLocal); + + mfem::Vector elementEnthalpy; + mfem::Vector quadratureEnthalpy; + + const int dimension = m_fem.mesh->Dimension(); + + const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering(); + + for (ElementPAData &data : m_elements) { + enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy); + + if (data.enthalpyDofTransformation != nullptr) { + data.enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy); + } + + const int quadraturePointCount = data.enthalpyBasis.Height(); + + const int enthalpyDofCount = data.enthalpyBasis.Width(); + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + + const int scalarDisplacementDofCount = displacementElement.GetDof(); + + const int displacementDofCount = data.displacementDofs.Size(); + + MFEM_VERIFY( + data.quadratureWeights.Size() == quadraturePointCount && + static_cast(data.physicalTestGradients.size()) == quadraturePointCount && + displacementDofCount == scalarDisplacementDofCount * dimension, + "Prepared pressure-force material state has stale " + "geometry data." + ); + + quadratureEnthalpy.SetSize(quadraturePointCount); + + data.enthalpyBasis.Mult(elementEnthalpy, quadratureEnthalpy); + + data.pressure.SetSize(quadraturePointCount); + + data.pressureDerivative.SetSize(quadraturePointCount); + + data.elementResidual.SetSize(displacementDofCount); + + data.elementResidual = 0.0; + + data.enthalpyJacobian.SetSize(displacementDofCount, enthalpyDofCount); + + data.enthalpyJacobian = 0.0; + + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const double enthalpy = quadratureEnthalpy(quadraturePoint); + + const double pressure = m_equationOfState.pressure_from_enthalpy(enthalpy); + + const double pressureDerivative = m_equationOfState.pressure_derivative_from_enthalpy(enthalpy); + + const double quadratureWeight = data.quadratureWeights(quadraturePoint); + + MFEM_VERIFY( + std::isfinite(pressure) && std::isfinite(pressureDerivative), + "Prepared pressure-force material state encountered " + "a non-finite EOS value." + ); + + data.pressure(quadraturePoint) = pressure; + + data.pressureDerivative(quadraturePoint) = pressureDerivative; + + const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint]; + + MFEM_VERIFY( + physicalTestGradient.Height() == scalarDisplacementDofCount && + physicalTestGradient.Width() == dimension, + "Prepared pressure-force material state has an " + "invalid physical test-gradient matrix." + ); + + for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) { + for (int component = 0; component < dimension; ++component) { + const int vectorDof = vector_dof_index( + displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension + ); + + const double weightedTestGradient = + quadratureWeight * physicalTestGradient(scalarDof, component); + + data.elementResidual(vectorDof) -= pressure * weightedTestGradient; + + for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) { + data.enthalpyJacobian(vectorDof, enthalpyDof) -= + pressureDerivative * weightedTestGradient * + data.enthalpyBasis(quadraturePoint, enthalpyDof); + } + } + } + } + } + + ++m_enthalpyJacobianStatistics.preparations; + } + + void PreparedPressureForceOperator::FinalizeDisplacementJacobianPreparation() { + const int dimension = m_fem.mesh->Dimension(); + + for (const ElementPAData &data : m_elements) { + const int quadraturePointCount = data.integrationRule->GetNPoints(); + + MFEM_VERIFY( + data.baseDisplacementData.has_value() && data.compactificationData.has_value() && + static_cast(data.baseMappingContexts.size()) == quadraturePointCount && + static_cast(data.referenceTestGradients.size()) == quadraturePointCount && + static_cast(data.physicalTestGradients.size()) == quadraturePointCount && + data.quadratureWeights.Size() == quadraturePointCount && + data.pressure.Size() == quadraturePointCount, + "Prepared pressure-force displacement Jacobian has " + "inconsistent frozen data." + ); + + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + MFEM_VERIFY( + data.referenceTestGradients[quadraturePoint].Width() == dimension && + data.physicalTestGradients[quadraturePoint].Width() == dimension, + "Prepared pressure-force displacement Jacobian has " + "a gradient with the wrong dimension." + ); + } + } + + ++m_displacementJacobianStatistics.preparations; + } + + void PreparedPressureForceOperator::AssembleCachedResidual() { + mfem::Vector localResidual(m_fem.displacementFes->GetVSize()); + + localResidual = 0.0; + + mfem::Vector elementResidual; + + for (const ElementPAData &data : m_elements) { + elementResidual = data.elementResidual; + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->TransformDual(elementResidual); + } + + localResidual.AddElementVector(data.displacementDofs, elementResidual); + } + + local_to_true(*m_fem.displacementFes, localResidual, m_fullDisplacementAction); + + m_cachedResidual.SetSize(m_displacementMap.reduced_size()); + + /* + * FieldDofMap::gather does not resize its destination. + */ + m_displacementMap.gather(m_fullDisplacementAction, m_cachedResidual); + } + + void PreparedPressureForceOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + + residual = m_cachedResidual; + + ++m_residualApplicationCount; + } + + void PreparedPressureForceOperator::ApplyEnthalpyJacobianAction( + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + MFEM_VERIFY( + enthalpyVariation.Size() == m_enthalpyMap.reduced_size(), + "Prepared pressure-force enthalpy variation has the wrong " + "supported size." + ); + + m_enthalpyMap.scatter(enthalpyVariation, m_enthalpyVariationTrue); + + mfem::Vector enthalpyVariationLocal; + + true_to_local(*m_fem.enthalpyFes, m_enthalpyVariationTrue, enthalpyVariationLocal); + + mfem::Vector localAction(m_fem.displacementFes->GetVSize()); + + localAction = 0.0; + + mfem::Vector elementVariation; + mfem::Vector elementAction; + + for (const ElementPAData &data : m_elements) { + enthalpyVariationLocal.GetSubVector(data.enthalpyDofs, elementVariation); + + if (data.enthalpyDofTransformation != nullptr) { + data.enthalpyDofTransformation->InvTransformPrimal(elementVariation); + } + + elementAction.SetSize(data.enthalpyJacobian.Height()); + + data.enthalpyJacobian.Mult(elementVariation, elementAction); + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->TransformDual(elementAction); + } + + localAction.AddElementVector(data.displacementDofs, elementAction); + } + + local_to_true(*m_fem.displacementFes, localAction, m_fullDisplacementAction); + + action.SetSize(m_displacementMap.reduced_size()); + + m_displacementMap.gather(m_fullDisplacementAction, action); + + ++m_enthalpyJacobianStatistics.applications; + } + + void PreparedPressureForceOperator::ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + MFEM_VERIFY( + displacementVariation.Size() == m_displacementMap.reduced_size(), + "Prepared pressure-force displacement variation has the " + "wrong supported size." + ); + + m_displacementMap.scatter(displacementVariation, m_displacementVariationTrue); + + mfem::Vector displacementVariationLocal; + + true_to_local(*m_fem.displacementFes, m_displacementVariationTrue, displacementVariationLocal); + + mfem::Vector localAction(m_fem.displacementFes->GetVSize()); + + localAction = 0.0; + + mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension()); + + mfem::Vector elementDisplacementVariation; + mfem::Vector elementAction; + + mfem::DenseMatrix physicalTestGradientVariation; + + const int dimension = m_fem.mesh->Dimension(); + + const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering(); + + for (const ElementPAData &data : m_elements) { + MFEM_VERIFY( + data.baseDisplacementData.has_value() && data.compactificationData.has_value() && + data.integrationRule != nullptr, + "Prepared pressure-force displacement Jacobian has " + "invalid frozen element data." + ); + + mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId); + + MFEM_VERIFY( + transformation != nullptr, "Prepared pressure-force displacement Jacobian received " + "a null element transformation." + ); + + displacementVariationLocal.GetSubVector(data.displacementDofs, elementDisplacementVariation); + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation); + } + + const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId); + + const mapping::ElementDisplacementData directionData = + mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation); + + const mapping::ElementMappingData mappingData{ + .displacement = *data.baseDisplacementData, .compactification = *data.compactificationData + }; + + const int quadraturePointCount = data.integrationRule->GetNPoints(); + + const int scalarDisplacementDofCount = displacementElement.GetDof(); + + MFEM_VERIFY( + static_cast(data.baseMappingContexts.size()) == quadraturePointCount && + data.pressure.Size() == quadraturePointCount, + "Prepared pressure-force displacement Jacobian has " + "stale quadrature data." + ); + + elementAction.SetSize(data.displacementDofs.Size()); + + elementAction = 0.0; + + physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension); + + for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) { + const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint); + + transformation->SetIntPoint(&integrationPoint); + + mapping::VolumeMappingVariation variation; + + const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation( + mappingData, directionData, *transformation, integrationPoint, + data.baseMappingContexts[quadraturePoint], workspace, variation + ); + + MFEM_VERIFY( + mappingStatus == mapping::MappingStatus::valid, + "Stateless mapping variation failed while applying " + "the prepared pressure-force displacement Jacobian. " + "Element: " + << data.elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadraturePoint << ", status: " << static_cast(mappingStatus) + ); + + mfem::Mult( + data.referenceTestGradients[quadraturePoint], variation.inverse_element_jacobian_variation, + physicalTestGradientVariation + ); + + const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint]; + + for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) { + for (int component = 0; component < dimension; ++component) { + const int vectorDof = vector_dof_index( + displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension + ); + + const double gradientWeightVariation = + data.quadratureWeights(quadraturePoint) * + physicalTestGradientVariation(scalarDof, component) + + variation.weight_variation * physicalTestGradient(scalarDof, component); + + const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation; + + MFEM_VERIFY( + std::isfinite(gradientWeightVariation) && std::isfinite(contribution), + "Prepared pressure-force displacement " + "Jacobian encountered a non-finite " + "contribution." + ); + + elementAction(vectorDof) -= contribution; + } + } + } + + if (data.displacementDofTransformation != nullptr) { + data.displacementDofTransformation->TransformDual(elementAction); + } + + localAction.AddElementVector(data.displacementDofs, elementAction); + } + + local_to_true(*m_fem.displacementFes, localAction, m_fullDisplacementAction); + + action.SetSize(m_displacementMap.reduced_size()); + + m_displacementMap.gather(m_fullDisplacementAction, action); + + ++m_displacementJacobianStatistics.applications; + } + + void PreparedPressureForceOperator::ApplyCompleteJacobianAction( + const mfem::Vector &enthalpyVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + mfem::Vector displacementAction; + + ApplyEnthalpyJacobianAction(enthalpyVariation, action); + + ApplyDisplacementJacobianAction(displacementVariation, displacementAction); + + MFEM_VERIFY( + action.Size() == displacementAction.Size(), "Prepared pressure-force complete Jacobian produced " + "incompatible column actions." + ); + + action += displacementAction; + + ++m_completeJacobianStatistics.applications; + } + + bool PreparedPressureForceOperator::IsPrepared() const noexcept { + return m_isPrepared && m_context.IsPrepared(); + } + + int PreparedPressureForceOperator::GetEnthalpySize() const noexcept { + return m_enthalpyMap.reduced_size(); + } + + int PreparedPressureForceOperator::GetDisplacementSize() const noexcept { + return m_displacementMap.reduced_size(); + } + + const context::pressure_force::PressureForceLinearizationContext & + PreparedPressureForceOperator::GetContext() const noexcept { + return m_context; + } + + const context::pressure_force::PressureForcePreparationStatistics & + PreparedPressureForceOperator::GetContextPreparationStatistics() const noexcept { + return m_context.GetPreparationStatistics(); + } + + std::uint64_t PreparedPressureForceOperator::GetResidualPreparationCount() const noexcept { + return m_residualPreparationCount; + } + + std::uint64_t PreparedPressureForceOperator::GetResidualApplicationCount() const noexcept { + return m_residualApplicationCount; + } + + const PreparedPressureForceEnthalpyJacobianStatistics & + PreparedPressureForceOperator::GetEnthalpyJacobianStatistics() const noexcept { + return m_enthalpyJacobianStatistics; + } + + const PreparedPressureForceDisplacementJacobianStatistics & + PreparedPressureForceOperator::GetDisplacementJacobianStatistics() const noexcept { + return m_displacementJacobianStatistics; + } + + const PreparedPressureForceCompleteJacobianStatistics & + PreparedPressureForceOperator::GetCompleteJacobianStatistics() const noexcept { + return m_completeJacobianStatistics; + } + + std::size_t PreparedPressureForceOperator::GetStellarElementCount() const noexcept { + return m_elements.size(); + } + + const fem::FEM &PreparedPressureForceOperator::GetFEM() const noexcept { + return m_fem; + } + + void PreparedPressureForceOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedPressureForceOperator must be prepared before " + "residual or Jacobian application." + ); + } + + PreparedPressureForceJacobianOperator::PreparedPressureForceJacobianOperator( + const BarotropicEquilibriumLayout &layout, + const PreparedPressureForceOperator &preparedOperator + ) + : mfem::Operator( + layout.residual_offsets().Last(), + layout.value_offsets().Last() + ), + m_layout(layout), + m_preparedOperator(preparedOperator) { + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + /* + * This adapter consumes only d and h and contributes only R_d. + * + * Do not impose GetTrueVSize() assumptions on unrelated root + * blocks. In particular rho and h may now be reduced FieldDof + * coordinates. + */ + MFEM_VERIFY( + m_layout.size(displacementValue) == m_preparedOperator.GetDisplacementSize(), + "Prepared pressure-force MFEM adapter received an " + "incompatible displacement value block." + ); + + MFEM_VERIFY( + m_layout.size(enthalpyValue) == m_preparedOperator.GetEnthalpySize(), + "Prepared pressure-force MFEM adapter received an " + "incompatible enthalpy value block." + ); + + MFEM_VERIFY( + m_layout.size(displacementResidual) == m_preparedOperator.GetDisplacementSize(), + "Prepared pressure-force MFEM adapter received an " + "incompatible displacement residual block." + ); + + MFEM_VERIFY( + Height() == m_layout.residual_offsets().Last() && Width() == m_layout.value_offsets().Last(), + "Prepared pressure-force MFEM adapter has inconsistent " + "operator dimensions." + ); + } + + void PreparedPressureForceJacobianOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + MFEM_VERIFY( + m_preparedOperator.IsPrepared(), "Prepared pressure-force MFEM adapter requires a prepared " + "pressure-force operator." + ); + + MFEM_VERIFY( + direction.Size() == Width(), "Prepared pressure-force MFEM adapter received a direction " + "with the wrong size." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + /* + * MFEM does not provide a const non-owning Vector view. + * These alias the packed direction but are passed only through + * const references. + */ + const mfem::Vector displacementVariation( + const_cast(direction.GetData()) + m_layout.offset(displacementValue), + m_layout.size(displacementValue) + ); + + const mfem::Vector enthalpyVariation( + const_cast(direction.GetData()) + m_layout.offset(enthalpyValue), + m_layout.size(enthalpyValue) + ); + + mfem::Vector displacementAction; + + m_preparedOperator.ApplyCompleteJacobianAction(enthalpyVariation, displacementVariation, displacementAction); + + MFEM_VERIFY( + displacementAction.Size() == m_layout.size(displacementResidual), + "Prepared pressure-force MFEM adapter produced a " + "displacement action with the wrong size." + ); + + action.SetSize(Height()); + + action = 0.0; + + const int residualOffset = m_layout.offset(displacementResidual); + + for (int entry = 0; entry < displacementAction.Size(); ++entry) { + action(residualOffset + entry) = displacementAction(entry); + } + } + + const BarotropicEquilibriumLayout &PreparedPressureForceJacobianOperator::GetLayout() const noexcept { + return m_layout; + } +} // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp b/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp new file mode 100644 index 0000000..23e06aa --- /dev/null +++ b/libmeanfield/impl/operators/prepared_rotation_displacement_force.cpp @@ -0,0 +1,288 @@ +module; + +#include + +module mean_field; + +import :operators.kernels.rotational_displacement_force; +import :operators.prepared_rotational_displacement_force; + +namespace mean_field::operators { + PreparedRotationalDisplacementForceOperator::PreparedRotationalDisplacementForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper + ) + : m_fem(f), + m_domainMapper(domainMapper), + m_context( + f, + domainMapper + ) { + MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedRotationalDisplacementForceOperator requires a mesh."); + + MFEM_VERIFY( + m_fem.mesh->Dimension() == 3, "PreparedRotationalDisplacementForceOperator requires a " + "three-dimensional mesh." + ); + + MFEM_VERIFY( + m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr, + "PreparedRotationalDisplacementForceOperator requires density " + "and displacement finite-element spaces." + ); + + MFEM_VERIFY( + m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr, + "PreparedRotationalDisplacementForceOperator requires the " + "compactification coordinate." + ); + + MFEM_VERIFY( + m_fem.quadratureFactory != nullptr, "PreparedRotationalDisplacementForceOperator requires the " + "quadrature-rule factory." + ); + + MFEM_VERIFY( + m_domainMapper.GetDimension() == m_fem.mesh->Dimension(), + "PreparedRotationalDisplacementForceOperator received a mapper " + "with the wrong dimension." + ); + } + + PreparedRotationalDisplacementForceReport PreparedRotationalDisplacementForceOperator::Prepare( + const context::rotational_displacement_force::RotationalDisplacementForceStateView &state, + const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies, + const physics::RigidRotation &rotation + ) { + const bool rotationChanged = + !m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation; + + PreparedRotationalDisplacementForceReport report; + report.contextReport = m_context.Prepare(state, dependencies); + + if (!report.contextReport.DidAnyWork()) { + return report; + } + + m_isPrepared = false; + + if (rotationChanged) { + m_rotation = rotation; + report.updatedRotation = true; + } + + MFEM_VERIFY( + m_rotation.has_value(), "PreparedRotationalDisplacementForceOperator has no frozen " + "rotation state." + ); + + if (report.contextReport.preparedBaseState) { + kernels::apply_rotational_displacement_force_residual( + m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_context.GetDisplacementTrue(), + m_cachedResidual + ); + + ++m_residualPreparationCount; + report.preparedResidual = true; + } + + MFEM_VERIFY( + m_cachedResidual.Size() == m_fem.displacementFes->GetTrueVSize(), + "The prepared rotational-displacement-force residual has the " + "wrong size." + ); + + m_preparedDependencies = dependencies; + m_isPrepared = true; + + return report; + } + + void PreparedRotationalDisplacementForceOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + residual = m_cachedResidual; + ++m_residualApplicationCount; + } + + void PreparedRotationalDisplacementForceOperator::ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_rotational_displacement_force_density_action( + m_fem, m_domainMapper, *m_rotation, densityVariation, m_context.GetDisplacementTrue(), action + ); + + ++m_densityJacobianStatistics.applications; + } + + void PreparedRotationalDisplacementForceOperator::ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_rotational_displacement_force_displacement_action( + m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), displacementVariation, + m_context.GetDisplacementTrue(), action + ); + + ++m_displacementJacobianStatistics.applications; + } + + void PreparedRotationalDisplacementForceOperator::ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const { + VerifyPrepared(); + + kernels::apply_rotational_displacement_force_complete_action( + m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), densityVariation, displacementVariation, + m_context.GetDisplacementTrue(), action + ); + + ++m_densityJacobianStatistics.applications; + ++m_displacementJacobianStatistics.applications; + ++m_completeJacobianStatistics.applications; + } + + bool PreparedRotationalDisplacementForceOperator::IsPrepared() const noexcept { + return m_isPrepared && m_rotation.has_value() && m_context.MatchesDependencies(m_preparedDependencies); + } + + const context::rotational_displacement_force::RotationalDisplacementForcePreparationStatistics & + PreparedRotationalDisplacementForceOperator::GetContextPreparationStatistics() const noexcept { + return m_context.GetPreparationStatistics(); + } + + std::uint64_t PreparedRotationalDisplacementForceOperator::GetResidualPreparationCount() const noexcept { + return m_residualPreparationCount; + } + + std::uint64_t PreparedRotationalDisplacementForceOperator::GetResidualApplicationCount() const noexcept { + return m_residualApplicationCount; + } + + const PreparedRotationalDisplacementForceColumnStatistics & + PreparedRotationalDisplacementForceOperator::GetDensityJacobianStatistics() const noexcept { + return m_densityJacobianStatistics; + } + + const PreparedRotationalDisplacementForceColumnStatistics & + PreparedRotationalDisplacementForceOperator::GetDisplacementJacobianStatistics() const noexcept { + return m_displacementJacobianStatistics; + } + + const PreparedRotationalDisplacementForceCompleteStatistics & + PreparedRotationalDisplacementForceOperator::GetCompleteJacobianStatistics() const noexcept { + return m_completeJacobianStatistics; + } + + const fem::FEM &PreparedRotationalDisplacementForceOperator::GetFEM() const noexcept { + return m_fem; + } + + const context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext & + PreparedRotationalDisplacementForceOperator::GetContext() const noexcept { + return m_context; + } + + void PreparedRotationalDisplacementForceOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedRotationalDisplacementForceOperator must be prepared " + "for the current revisions before residual or Jacobian " + "application." + ); + } + + PreparedRotationalDisplacementForceJacobianOperator::PreparedRotationalDisplacementForceJacobianOperator( + const RotationalDisplacementForceLayout &layout, + const PreparedRotationalDisplacementForceOperator &preparedOperator + ) + : mfem::Operator( + layout.residual_offsets().Last(), + layout.value_offsets().Last() + ), + m_layout(layout), + m_preparedOperator(preparedOperator) { + const fem::FEM &f = m_preparedOperator.GetFEM(); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + MFEM_VERIFY( + m_layout.size(densityValue) == f.densityFes->GetTrueVSize() && + m_layout.size(displacementValue) == f.displacementFes->GetTrueVSize() && + m_layout.size(displacementResidual) == f.displacementFes->GetTrueVSize(), + "Prepared rotational-displacement-force MFEM adapter received " + "incompatible coupled block sizes." + ); + } + + void PreparedRotationalDisplacementForceJacobianOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + MFEM_VERIFY( + m_preparedOperator.IsPrepared(), "Prepared rotational-displacement-force MFEM adapter requires " + "a prepared operator." + ); + + MFEM_VERIFY( + direction.Size() == Width(), "Prepared rotational-displacement-force MFEM adapter received " + "a direction with the wrong size." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + + const mfem::Vector densityVariation( + const_cast(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue) + ); + + const mfem::Vector displacementVariation( + const_cast(direction.GetData()) + m_layout.offset(displacementValue), + m_layout.size(displacementValue) + ); + + mfem::Vector displacementAction; + + m_preparedOperator.ApplyCompleteJacobianAction(densityVariation, displacementVariation, displacementAction); + + MFEM_VERIFY( + displacementAction.Size() == m_layout.size(displacementResidual), + "Prepared rotational-displacement-force MFEM adapter produced " + "a displacement action with the wrong size." + ); + + action.SetSize(Height()); + action = 0.0; + + const int residualOffset = m_layout.offset(displacementResidual); + + for (int entry = 0; entry < displacementAction.Size(); ++entry) { + action(residualOffset + entry) = displacementAction(entry); + } + } + + const RotationalDisplacementForceLayout & + PreparedRotationalDisplacementForceJacobianOperator::GetLayout() const noexcept { + return m_layout; + } +} // namespace mean_field::operators diff --git a/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp b/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp new file mode 100644 index 0000000..323a23c --- /dev/null +++ b/libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp @@ -0,0 +1,809 @@ +module; + +#include +#include +#include + +#include + +module mean_field; + +import :operators.prepared_stellar_equilibrium; +import :physics.gravity; + +namespace { + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + [[nodiscard]] mean_field::fem::FEM &ensure_gravity_static_operators(mean_field::fem::FEM &f) { + MFEM_VERIFY( + f.mesh != nullptr && f.densityFes != nullptr && f.displacementFes != nullptr && + f.gravityFluxFes != nullptr && f.gravityPotentialFes != nullptr && f.enthalpyFes != nullptr, + "PreparedStellarEquilibriumOperator requires the complete coupled finite-element discretization." + ); + + if (f.gravityContext.b_form == nullptr || f.gravityContext.BT == nullptr) { + mean_field::physics::update_stiffness_matrix(f); + } + + MFEM_VERIFY( + f.gravityContext.b_form != nullptr && f.gravityContext.BT != nullptr, + "PreparedStellarEquilibriumOperator could not initialize the static gravity divergence operators." + ); + + return f; + } + + [[nodiscard]] mean_field::operators::StellarEquilibriumLayout make_layout( + const mean_field::field::FieldDofMap &densityMap, + const mean_field::field::FieldDofMap &displacementMap, + const mean_field::field::FieldDofMap &gravityFluxMap, + const mean_field::field::FieldDofMap &gravityPotentialMap, + const mean_field::field::FieldDofMap &enthalpyMap + ) { + using Form = mean_field::utils::blocks::barotropic_equilibrium_form; + + const std::array valueSizes{ + densityMap.reduced_size(), displacementMap.reduced_size(), gravityFluxMap.reduced_size(), + gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1 + }; + + const std::array residualSizes{ + gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(), + displacementMap.reduced_size(), enthalpyMap.reduced_size(), 1 + }; + + return {valueSizes, residualSizes}; + } + + [[nodiscard]] mfem::Array make_gravity_state_offsets(const mean_field::fem::FEM &f) { + mfem::Array offsets(5); + offsets[0] = 0; + offsets[1] = offsets[0] + f.densityFes->GetTrueVSize(); + offsets[2] = offsets[1] + f.displacementFes->GetTrueVSize(); + offsets[3] = offsets[2] + f.gravityFluxFes->GetTrueVSize(); + offsets[4] = offsets[3] + f.gravityPotentialFes->GetTrueVSize(); + return offsets; + } + + [[nodiscard]] mfem::Array make_gravity_residual_offsets(const mean_field::fem::FEM &f) { + mfem::Array offsets(3); + offsets[0] = 0; + offsets[1] = f.gravityFluxFes->GetTrueVSize(); + offsets[2] = offsets[1] + f.gravityPotentialFes->GetTrueVSize(); + return offsets; + } + + template + [[nodiscard]] mfem::Vector make_value_view( + const mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::value_block block + ) { + MFEM_VERIFY( + vector.Size() == layout.value_offsets().Last(), + "The coupled vector does not match the stellar-equilibrium value layout." + ); + + return mfem::Vector(const_cast(vector.GetData()) + layout.offset(block), layout.size(block)); + } + + template + [[nodiscard]] mfem::Vector make_residual_view( + mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + MFEM_VERIFY( + vector.Size() == layout.residual_offsets().Last(), + "The coupled vector does not match the stellar-equilibrium residual layout." + ); + + return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block)); + } + + template + void assign_residual_block( + mfem::Vector &coupledResidual, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block, + const mfem::Vector &blockResidual, + const char *message + ) { + MFEM_VERIFY(layout.size(block) == blockResidual.Size(), message); + mfem::Vector destination = make_residual_view(coupledResidual, layout, block); + destination = blockResidual; + } + + void assign_gravity_block( + mfem::Vector &gravityState, + const mfem::Array &offsets, + const int blockIndex, + const mfem::Vector &source, + const char *message + ) { + MFEM_VERIFY(offsets.Size() == 5, "Gravity state offsets are invalid."); + MFEM_VERIFY(blockIndex >= 0 && blockIndex + 1 < offsets.Size(), "Requested gravity-state block is invalid."); + + const int blockSize = offsets[blockIndex + 1] - offsets[blockIndex]; + MFEM_VERIFY(blockSize == source.Size(), message); + MFEM_VERIFY(gravityState.Size() == offsets.Last(), "Packed gravity state has the wrong size."); + + mfem::Vector destination(gravityState.GetData() + offsets[blockIndex], blockSize); + destination = source; + } + + void pack_gravity_vector( + mfem::Vector &gravityState, + const mfem::Array &offsets, + const mfem::Vector &density, + const mfem::Vector &displacement, + const mfem::Vector &gravityGradient, + const mfem::Vector &gravityPotential + ) { + MFEM_VERIFY(offsets.Size() == 5, "Packed gravity state requires four blocks."); + if (gravityState.Size() != offsets.Last()) { + gravityState.SetSize(offsets.Last()); + } + + assign_gravity_block( + gravityState, offsets, 0, density, "The full density vector has the wrong gravity-state size." + ); + assign_gravity_block( + gravityState, offsets, 1, displacement, "The displacement vector has the wrong gravity-state size." + ); + assign_gravity_block( + gravityState, offsets, 2, gravityGradient, "The gravity-gradient vector has the wrong gravity-state size." + ); + assign_gravity_block( + gravityState, offsets, 3, gravityPotential, "The gravity-potential vector has the wrong gravity-state size." + ); + } + + void validate_finite_vector( + const mfem::Vector &vector, + const char *message + ) { + for (int index = 0; index < vector.Size(); ++index) { + MFEM_VERIFY(std::isfinite(vector(index)), message); + } + } + + void validate_dependency_transition( + const mean_field::operators::StellarEquilibriumDependencyStamp &prepared, + const mean_field::operators::StellarEquilibriumDependencyStamp &requested, + const char *message + ) { + MFEM_VERIFY(prepared.identity != requested.identity || requested.revision >= prepared.revision, message); + MFEM_VERIFY( + prepared.identity == requested.identity || prepared.revision != requested.revision, + "A new stellar-equilibrium dependency identity must also carry a visibly different revision." + ); + } + + [[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions + make_gravity_revisions(const mean_field::operators::StellarEquilibriumDependencies &dependencies) { + return { + .discretization = {.value = dependencies.discretization.revision}, + .displacement = {.value = dependencies.displacement.revision}, + .density = {.value = dependencies.density.revision}, + .gravity_gradient = {.value = dependencies.gravityGradient.revision}, + .gravity_potential = {.value = dependencies.gravityPotential.revision} + }; + } + + [[nodiscard]] mean_field::operators::context::barotropic::BarotropicClosureDependencies + make_barotropic_closure_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision}, + .enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}, + .displacement = { + .identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision + } + }; + } + + [[nodiscard]] mean_field::operators::DisplacementResidualDependencies + make_displacement_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision}, + .displacement = + {.identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision}, + .gravityGradient = + {.identity = dependencies.gravityGradient.identity, .revision = dependencies.gravityGradient.revision}, + .enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}, + .rotation = {.identity = dependencies.rotation.identity, .revision = dependencies.rotation.revision} + }; + } + + [[nodiscard]] mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies + make_hydrostatic_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}, + .gravityPotential = + {.identity = dependencies.gravityPotential.identity, + .revision = dependencies.gravityPotential.revision}, + .displacement = + {.identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision}, + .rotation = {.identity = dependencies.rotation.identity, .revision = dependencies.rotation.revision}, + .bernoulliConstant = { + .identity = dependencies.bernoulliConstant.identity, .revision = dependencies.bernoulliConstant.revision + } + }; + } + + [[nodiscard]] mean_field::operators::MassNormalizationDependencies + make_mass_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) { + return { + .discretization = + {.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision}, + .density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision}, + .displacement = + {.identity = dependencies.displacement.identity, .revision = dependencies.displacement.revision}, + .targetMass = {.identity = dependencies.targetMass.identity, .revision = dependencies.targetMass.revision} + }; + } +} // namespace + +namespace mean_field::operators { + struct PreparedStellarEquilibriumOperator::ConstructionData { + field::FieldDofMap densityMap; + field::FieldDofMap displacementMap; + field::FieldDofMap gravityFluxMap; + field::FieldDofMap gravityPotentialMap; + field::FieldDofMap enthalpyMap; + + StellarEquilibriumLayout layout; + mfem::Array gravityStateOffsets; + mfem::Array gravityResidualOffsets; + + explicit ConstructionData(fem::FEM &f) + : densityMap( + field::make_field_dof_map< + field::Density, + DomainSchema>(*f.densityFes) + ), + displacementMap( + field::make_field_dof_map< + field::Displacement, + DomainSchema>(*f.displacementFes) + ), + gravityFluxMap( + field::make_field_dof_map< + field::Gravity, + DomainSchema>(*f.gravityFluxFes) + ), + gravityPotentialMap( + field::make_field_dof_map< + field::Gravity, + DomainSchema>(*f.gravityPotentialFes) + ), + enthalpyMap( + field::make_field_dof_map< + field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ), + layout(make_layout( + densityMap, + displacementMap, + gravityFluxMap, + gravityPotentialMap, + enthalpyMap + )), + gravityStateOffsets(make_gravity_state_offsets(f)), + gravityResidualOffsets(make_gravity_residual_offsets(f)) { + } + }; + + PreparedStellarEquilibriumOperator::ConstructionData + PreparedStellarEquilibriumOperator::MakeConstructionData(fem::FEM &f) { + ensure_gravity_static_operators(f); + return ConstructionData(f); + } + + PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + const models::StellarModel &stellarModel + ) + : PreparedStellarEquilibriumOperator( + f, + domainMapper, + equationOfState, + stellarModel.targetMass() + ) { + } + + PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + const double targetMass + ) + : PreparedStellarEquilibriumOperator( + f, + domainMapper, + equationOfState, + targetMass, + MakeConstructionData(f) + ) { + } + + PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + const double targetMass, + ConstructionData constructionData + ) + : mfem::Operator( + constructionData.layout.residual_offsets().Last(), + constructionData.layout.value_offsets().Last() + ), + m_layout(constructionData.layout), + m_gravityStateOffsets(constructionData.gravityStateOffsets), + m_gravityContext( + f, + domainMapper + ), + m_gravityJacobianOperator( + f, + domainMapper, + m_gravityContext, + m_gravityStateOffsets, + constructionData.gravityResidualOffsets + ), + m_gravityOperator( + f, + domainMapper, + m_gravityContext, + m_gravityStateOffsets, + m_gravityJacobianOperator + ), + m_barotropicClosureOperator( + f, + domainMapper, + equationOfState + ), + m_hydrostaticOperator( + f, + domainMapper + ), + m_displacementOperator( + f, + domainMapper, + equationOfState, + m_gravityContext + ), + m_massNormalizationOperator( + f, + domainMapper, + m_gravityContext + ), + m_targetMass(targetMass), + m_densityMap(std::move(constructionData.densityMap)), + m_displacementMap(std::move(constructionData.displacementMap)), + m_gravityFluxMap(std::move(constructionData.gravityFluxMap)), + m_gravityPotentialMap(std::move(constructionData.gravityPotentialMap)), + m_enthalpyMap(std::move(constructionData.enthalpyMap)) { + MFEM_VERIFY( + std::isfinite(m_targetMass) && m_targetMass > 0.0, + "PreparedStellarEquilibriumOperator requires a finite, positive target mass." + ); + + MFEM_VERIFY( + Width() == m_layout.value_offsets().Last() && Height() == m_layout.residual_offsets().Last(), + "PreparedStellarEquilibriumOperator has inconsistent block dimensions." + ); + + MFEM_VERIFY( + m_displacementMap.is_identity(), "PreparedStellarEquilibriumOperator currently requires Displacement " + "support to span the full MFEM true-DOF space." + ); + MFEM_VERIFY( + m_gravityFluxMap.is_identity(), "PreparedStellarEquilibriumOperator currently requires gravity-flux " + "support to span the full MFEM true-DOF space." + ); + MFEM_VERIFY( + m_gravityPotentialMap.is_identity(), "PreparedStellarEquilibriumOperator currently requires " + "gravity-potential support to span the full MFEM true-DOF space." + ); + + m_fullDensity.SetSize(m_densityMap.full_size()); + m_fullEnthalpy.SetSize(m_enthalpyMap.full_size()); + m_fullGravityState.SetSize(m_gravityStateOffsets.Last()); + + m_fullDensityVariation.SetSize(m_densityMap.full_size()); + m_fullEnthalpyVariation.SetSize(m_enthalpyMap.full_size()); + m_fullGravityDirection.SetSize(m_gravityStateOffsets.Last()); + m_fullEnthalpyAction.SetSize(m_enthalpyMap.full_size()); + + m_fullDensity = 0.0; + m_fullEnthalpy = 0.0; + m_fullGravityState = 0.0; + m_fullDensityVariation = 0.0; + m_fullEnthalpyVariation = 0.0; + m_fullGravityDirection = 0.0; + m_fullEnthalpyAction = 0.0; + } + + PreparedStellarEquilibriumReport PreparedStellarEquilibriumOperator::Prepare( + const mfem::Vector &state, + const StellarEquilibriumDependencies &dependencies, + const physics::RigidRotation &rotation + ) { + MFEM_VERIFY( + state.Size() == Width(), "PreparedStellarEquilibriumOperator received a state with the wrong size." + ); + validate_finite_vector(state, "PreparedStellarEquilibriumOperator received a non-finite state."); + + const bool wasPrepared = m_isPrepared; + if (wasPrepared) { + validate_dependency_transition( + m_preparedDependencies.discretization, dependencies.discretization, + "The discretization revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.density, dependencies.density, "The density revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.displacement, dependencies.displacement, + "The displacement revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.gravityGradient, dependencies.gravityGradient, + "The gravity-gradient revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.gravityPotential, dependencies.gravityPotential, + "The gravity-potential revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.enthalpy, dependencies.enthalpy, "The enthalpy revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.bernoulliConstant, dependencies.bernoulliConstant, + "The Bernoulli-constant revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.rotation, dependencies.rotation, "The rotation revision cannot move backwards." + ); + validate_dependency_transition( + m_preparedDependencies.targetMass, dependencies.targetMass, + "The target-mass revision cannot move backwards." + ); + } + + m_isPrepared = false; + + using Form = utils::blocks::barotropic_equilibrium_form; + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto bernoulliValue = + utils::blocks::get_value_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + const mfem::Vector reducedDensity = make_value_view(state, m_layout, densityValue); + const mfem::Vector displacement = make_value_view(state, m_layout, displacementValue); + const mfem::Vector gravityGradient = make_value_view(state, m_layout, gravityGradientValue); + const mfem::Vector gravityPotential = make_value_view(state, m_layout, gravityPotentialValue); + const mfem::Vector reducedEnthalpy = make_value_view(state, m_layout, enthalpyValue); + const mfem::Vector bernoulli = make_value_view(state, m_layout, bernoulliValue); + + m_densityMap.scatter(reducedDensity, m_fullDensity); + m_enthalpyMap.scatter(reducedEnthalpy, m_fullEnthalpy); + + pack_gravity_vector( + m_fullGravityState, m_gravityStateOffsets, m_fullDensity, displacement, gravityGradient, gravityPotential + ); + + PreparedStellarEquilibriumReport report; + + report.gravity = m_gravityOperator.Prepare(m_fullGravityState, make_gravity_revisions(dependencies)); + + report.barotropicClosure = m_barotropicClosureOperator.Prepare( + {.density = reducedDensity, .enthalpy = reducedEnthalpy, .displacement = displacement}, + make_barotropic_closure_dependencies(dependencies) + ); + + report.hydrostatic = m_hydrostaticOperator.Prepare( + {.enthalpy = m_fullEnthalpy, + .gravityPotential = gravityPotential, + .displacement = displacement, + .bernoulliConstant = bernoulli(0)}, + make_hydrostatic_dependencies(dependencies), rotation + ); + + report.displacement = m_displacementOperator.Prepare( + {.enthalpy = reducedEnthalpy}, make_displacement_dependencies(dependencies), rotation + ); + + report.massNormalization = + m_massNormalizationOperator.Prepare({.targetMass = m_targetMass}, make_mass_dependencies(dependencies)); + + const bool dependenciesChanged = !wasPrepared || dependencies != m_preparedDependencies; + if (dependenciesChanged || report.DidAnyChildWork()) { + AssembleResidual(); + report.assembledResidual = true; + } + + m_preparedDependencies = dependencies; + m_isPrepared = true; + return report; + } + + void PreparedStellarEquilibriumOperator::AssembleResidual() { + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto gravityGradientResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); + constexpr auto densityResidual = + utils::blocks::get_residual_block(utils::blocks::density_field.mass_term); + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + constexpr auto enthalpyResidual = + utils::blocks::get_residual_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto massResidual = + utils::blocks::get_residual_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + mfem::Vector gravity; + mfem::Vector closure; + mfem::Vector displacement; + mfem::Vector mass; + + m_gravityOperator.Mult(m_fullGravityState, gravity); + m_barotropicClosureOperator.BuildResidual(closure); + m_displacementOperator.BuildResidual(displacement); + m_hydrostaticOperator.BuildResidual(m_fullEnthalpyAction); + m_massNormalizationOperator.BuildResidual(mass); + + m_cachedResidual.SetSize(Height()); + m_cachedResidual = 0.0; + + MFEM_VERIFY( + gravity.Size() == m_layout.size(gravityGradientResidual) + m_layout.size(gravityPotentialResidual), + "The gravity residual has the wrong size." + ); + + mfem::Vector gravityGradient(gravity.GetData(), m_layout.size(gravityGradientResidual)); + mfem::Vector gravityPotential( + gravity.GetData() + m_layout.size(gravityGradientResidual), m_layout.size(gravityPotentialResidual) + ); + + assign_residual_block( + m_cachedResidual, m_layout, gravityGradientResidual, gravityGradient, + "The gravity-gradient residual has the wrong size." + ); + assign_residual_block( + m_cachedResidual, m_layout, gravityPotentialResidual, gravityPotential, + "The gravity-potential residual has the wrong size." + ); + assign_residual_block( + m_cachedResidual, m_layout, densityResidual, closure, "The closure residual has the wrong size." + ); + assign_residual_block( + m_cachedResidual, m_layout, displacementResidual, displacement, + "The displacement residual has the wrong size." + ); + + { + mfem::Vector reducedEnthalpyResidual = make_residual_view(m_cachedResidual, m_layout, enthalpyResidual); + m_enthalpyMap.gather(m_fullEnthalpyAction, reducedEnthalpyResidual); + } + + assign_residual_block( + m_cachedResidual, m_layout, massResidual, mass, "The mass-normalization residual has the wrong size." + ); + + ++m_statistics.residualAssemblies; + } + + void PreparedStellarEquilibriumOperator::BuildResidual(mfem::Vector &residual) const { + VerifyPrepared(); + residual = m_cachedResidual; + ++m_statistics.residualApplications; + } + + void PreparedStellarEquilibriumOperator::Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const { + VerifyPrepared(); + MFEM_VERIFY( + direction.Size() == Width(), + "PreparedStellarEquilibriumOperator received a Jacobian direction with the wrong size." + ); + validate_finite_vector( + direction, "PreparedStellarEquilibriumOperator received a non-finite Jacobian direction." + ); + + using Form = utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = utils::blocks::get_value_block(utils::blocks::density_field.mass_term); + constexpr auto displacementValue = + utils::blocks::get_value_block(utils::blocks::displacement_field.geometry_term); + constexpr auto gravityGradientValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialValue = + utils::blocks::get_value_block(utils::blocks::gravity_field.poisson_term); + constexpr auto enthalpyValue = + utils::blocks::get_value_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto bernoulliValue = + utils::blocks::get_value_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + constexpr auto gravityGradientResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialResidual = + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); + constexpr auto densityResidual = + utils::blocks::get_residual_block(utils::blocks::density_field.mass_term); + constexpr auto displacementResidual = + utils::blocks::get_residual_block(utils::blocks::displacement_field.geometry_term); + constexpr auto enthalpyResidual = + utils::blocks::get_residual_block(utils::blocks::enthalpy_field.specific_term); + constexpr auto massResidual = + utils::blocks::get_residual_block(utils::blocks::barotropic_constant_field.mass_normalization_term); + + const mfem::Vector reducedDensityDirection = make_value_view(direction, m_layout, densityValue); + const mfem::Vector displacementDirection = make_value_view(direction, m_layout, displacementValue); + const mfem::Vector gravityGradientDirection = make_value_view(direction, m_layout, gravityGradientValue); + const mfem::Vector gravityPotentialDirection = make_value_view(direction, m_layout, gravityPotentialValue); + const mfem::Vector reducedEnthalpyDirection = make_value_view(direction, m_layout, enthalpyValue); + const mfem::Vector bernoulliDirection = make_value_view(direction, m_layout, bernoulliValue); + + m_densityMap.scatter(reducedDensityDirection, m_fullDensityVariation); + m_enthalpyMap.scatter(reducedEnthalpyDirection, m_fullEnthalpyVariation); + + pack_gravity_vector( + m_fullGravityDirection, m_gravityStateOffsets, m_fullDensityVariation, displacementDirection, + gravityGradientDirection, gravityPotentialDirection + ); + + mfem::Vector gravityAction; + mfem::Vector closureAction; + mfem::Vector displacementAction; + mfem::Vector massAction; + + m_gravityJacobianOperator.Mult(m_fullGravityDirection, gravityAction); + + m_barotropicClosureOperator.Mult( + reducedDensityDirection, reducedEnthalpyDirection, displacementDirection, closureAction + ); + + m_displacementOperator.ApplyCompleteJacobianAction( + m_fullDensityVariation, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection, + displacementAction + ); + + m_hydrostaticOperator.ApplyCompleteJacobianAction( + m_fullEnthalpyVariation, gravityPotentialDirection, bernoulliDirection(0), displacementDirection, + m_fullEnthalpyAction + ); + + m_massNormalizationOperator.ApplyCompleteJacobianAction( + m_fullDensityVariation, displacementDirection, massAction + ); + + action.SetSize(Height()); + action = 0.0; + + MFEM_VERIFY( + gravityAction.Size() == m_layout.size(gravityGradientResidual) + m_layout.size(gravityPotentialResidual), + "The gravity Jacobian action has the wrong size." + ); + + mfem::Vector gravityGradientAction(gravityAction.GetData(), m_layout.size(gravityGradientResidual)); + mfem::Vector gravityPotentialAction( + gravityAction.GetData() + m_layout.size(gravityGradientResidual), m_layout.size(gravityPotentialResidual) + ); + + assign_residual_block( + action, m_layout, gravityGradientResidual, gravityGradientAction, + "The gravity-gradient Jacobian action has the wrong size." + ); + assign_residual_block( + action, m_layout, gravityPotentialResidual, gravityPotentialAction, + "The gravity-potential Jacobian action has the wrong size." + ); + assign_residual_block( + action, m_layout, densityResidual, closureAction, "The closure Jacobian action has the wrong size." + ); + assign_residual_block( + action, m_layout, displacementResidual, displacementAction, + "The displacement Jacobian action has the wrong size." + ); + + { + mfem::Vector reducedEnthalpyAction = make_residual_view(action, m_layout, enthalpyResidual); + m_enthalpyMap.gather(m_fullEnthalpyAction, reducedEnthalpyAction); + } + + assign_residual_block( + action, m_layout, massResidual, massAction, "The mass-normalization Jacobian action has the wrong size." + ); + + ++m_statistics.jacobianApplications; + } + + bool PreparedStellarEquilibriumOperator::IsPrepared() const noexcept { + return m_isPrepared && m_gravityContext.IsPrepared() && m_barotropicClosureOperator.IsPrepared() && + m_hydrostaticOperator.IsPrepared() && m_displacementOperator.IsPrepared() && + m_massNormalizationOperator.IsPrepared(); + } + + double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept { + return m_targetMass; + } + + const StellarEquilibriumLayout &PreparedStellarEquilibriumOperator::GetLayout() const noexcept { + return m_layout; + } + + const StellarEquilibriumDependencies &PreparedStellarEquilibriumOperator::GetDependencies() const { + VerifyPrepared(); + return m_preparedDependencies; + } + + const PreparedStellarEquilibriumStatistics &PreparedStellarEquilibriumOperator::GetStatistics() const noexcept { + return m_statistics; + } + + const context::gravity_field::GravityFieldLinearizationContext & + PreparedStellarEquilibriumOperator::GetGravityContext() const noexcept { + return m_gravityContext; + } + + const GravityFieldOperator &PreparedStellarEquilibriumOperator::GetGravityOperator() const noexcept { + return m_gravityOperator; + } + + const GravityFieldJacobianOperator & + PreparedStellarEquilibriumOperator::GetGravityJacobianOperator() const noexcept { + return m_gravityJacobianOperator; + } + + const PreparedBarotropicClosureOperator & + PreparedStellarEquilibriumOperator::GetBarotropicClosureOperator() const noexcept { + return m_barotropicClosureOperator; + } + + const context::barotropic::BarotropicClosureLinearizationContext & + PreparedStellarEquilibriumOperator::GetBarotropicClosureContext() const noexcept { + return m_barotropicClosureOperator.GetContext(); + } + + const PreparedHydrostaticEquilibriumOperator & + PreparedStellarEquilibriumOperator::GetHydrostaticOperator() const noexcept { + return m_hydrostaticOperator; + } + + const PreparedDisplacementResidualOperator & + PreparedStellarEquilibriumOperator::GetDisplacementOperator() const noexcept { + return m_displacementOperator; + } + + const PreparedMassNormalizationOperator & + PreparedStellarEquilibriumOperator::GetMassNormalizationOperator() const noexcept { + return m_massNormalizationOperator; + } + + void PreparedStellarEquilibriumOperator::VerifyPrepared() const { + MFEM_VERIFY( + IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application." + ); + } +} // namespace mean_field::operators diff --git a/libmeanfield/impl/physics/gravity.cpp b/libmeanfield/impl/physics/gravity.cpp index e60e8a8..1320975 100644 --- a/libmeanfield/impl/physics/gravity.cpp +++ b/libmeanfield/impl/physics/gravity.cpp @@ -33,17 +33,15 @@ namespace { true_dofs.SetSize(finite_element_space.GetTrueVSize()); - const mfem::Operator *restriction = - finite_element_space.GetRestrictionMatrix(); + const mfem::Operator *restriction = finite_element_space.GetRestrictionMatrix(); if (restriction != nullptr) { restriction->Mult(grid_function, true_dofs); } else { MFEM_VERIFY( - grid_function.Size() == true_dofs.Size(), - "A finite-element space without a restriction operator must " - "have " - "matching local and true sizes." + grid_function.Size() == true_dofs.Size(), "A finite-element space without a restriction operator must " + "have " + "matching local and true sizes." ); true_dofs = grid_function; @@ -62,10 +60,7 @@ namespace mean_field::physics { f.densityFes != nullptr && rho.FESpace() == f.densityFes.get(), "Gravity solve requires rho to use the registered density space." ); - MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "Gravity solve requires the registered gravity-potential space." - ); + MFEM_VERIFY(f.gravityPotentialFes != nullptr, "Gravity solve requires the registered gravity-potential space."); mfem::Array outer_bdr_marker(f.mesh->bdr_attributes.Max()); outer_bdr_marker = 0; @@ -86,23 +81,16 @@ namespace mean_field::physics { return l2_multipole_potential(f, utils::MASS, x_physical); }; - boundary_potential_coeff = - std::make_unique(boundary_potential); + boundary_potential_coeff = std::make_unique(boundary_potential); auto boundary_integrator = - std::make_unique( - *boundary_potential_coeff - ); - const mfem::FiniteElement &boundary_element = - *f.gravityFluxFes->GetTypicalTraceElement(); + std::make_unique(*boundary_potential_coeff); + const mfem::FiniteElement &boundary_element = *f.gravityFluxFes->GetTypicalTraceElement(); f.quadratureFactory->configure_gravity_boundary( - *boundary_integrator, - quadrature::QuadratureRole::discretization, boundary_element, + *boundary_integrator, quadrature::QuadratureRole::discretization, boundary_element, utils::DOMAINS::VACUUM, quadrature::MappingKind::none ); - g_rhs.AddBoundaryIntegrator( - boundary_integrator.release(), outer_bdr_marker - ); + g_rhs.AddBoundaryIntegrator(boundary_integrator.release(), outer_bdr_marker); } g_rhs.Assemble(); @@ -112,36 +100,25 @@ namespace mean_field::physics { mfem::ParLinearForm f_rhs(f.gravityPotentialFes.get()); std::unique_ptr mapped_source_coeff; - mfem::Coefficient *active_source_coeff = &source_coeff; - quadrature::MappingKind source_mapping_kind = - quadrature::MappingKind::none; + mfem::Coefficient *active_source_coeff = &source_coeff; + quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none; if (f.has_mapping()) { - mapped_source_coeff = - std::make_unique( - *f.mapping, source_coeff - ); + mapped_source_coeff = std::make_unique(*f.mapping, source_coeff); active_source_coeff = mapped_source_coeff.get(); source_mapping_kind = quadrature::MappingKind::general; } - auto source_integrator = - std::make_unique(*active_source_coeff); - const mfem::FiniteElement &source_test_element = - *f.gravityPotentialFes->GetTypicalFE(); - const mfem::ElementTransformation &source_transformation = - *f.mesh->GetElementTransformation(0); - const int source_coefficient_order = f.densityFes->GetMaxElementOrder(); + auto source_integrator = std::make_unique(*active_source_coeff); + const mfem::FiniteElement &source_test_element = *f.gravityPotentialFes->GetTypicalFE(); + const mfem::ElementTransformation &source_transformation = *f.mesh->GetElementTransformation(0); + const int source_coefficient_order = f.densityFes->GetMaxElementOrder(); f.quadratureFactory->configure_gravity_source( - *source_integrator, quadrature::QuadratureRole::discretization, - source_test_element, source_transformation, - source_coefficient_order, utils::DOMAINS::STELLAR, - source_mapping_kind - ); - f_rhs.AddDomainIntegrator( - source_integrator.release(), f.gravityContext.stellar_mask + *source_integrator, quadrature::QuadratureRole::discretization, source_test_element, source_transformation, + source_coefficient_order, utils::DOMAINS::STELLAR, source_mapping_kind ); + f_rhs.AddDomainIntegrator(source_integrator.release(), f.gravityContext.stellar_mask); f_rhs.Assemble(); mfem::BlockVector RHS(f.gravityBlockTrueOffsets); @@ -177,13 +154,9 @@ namespace mean_field::physics { std::unique_ptr centrifugal_coeff; if (fem.has_mapping()) { - centrifugal_coeff = std::make_unique< - mapping::PhysicalPositionFunctionCoefficient>( - *fem.mapping, rot - ); + centrifugal_coeff = std::make_unique(*fem.mapping, rot); } else { - centrifugal_coeff = - std::make_unique(rot); + centrifugal_coeff = std::make_unique(rot); } mfem::GridFunction centrifugal_gf(fem.gravityPotentialFes.get()); @@ -207,19 +180,14 @@ namespace mean_field::physics { if (fem.mesh->GetAttribute(i) == 3) continue; - mfem::ElementTransformation *trans = - fem.mesh->GetElementTransformation(i); - using DensityField = field::Field; - const quadrature::Query query = - DensityField::make_query( - quadrature::QuadratureRole::diagnostic, trans->OrderW(), - std::array{2}, utils::DOMAINS::STELLAR, - fem.has_mapping() ? quadrature::MappingKind::general - : quadrature::MappingKind::none - ); + mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); + using DensityField = field::Field; + const quadrature::Query query = DensityField::make_query( + quadrature::QuadratureRole::diagnostic, trans->OrderW(), std::array{2}, utils::DOMAINS::STELLAR, + fem.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none + ); const mfem::IntegrationRule &ir = - *fem.quadratureFactory->get(query, trans->GetGeometryType()) - .integration_rule; + *fem.quadratureFactory->get(query, trans->GetGeometryType()).integration_rule; for (int j = 0; j < ir.GetNPoints(); ++j) { const mfem::IntegrationPoint &ip = ir.IntPoint(j); @@ -250,9 +218,8 @@ namespace mean_field::physics { for (int m = 0; m < dim; ++m) { for (int n = 0; n < dim; ++n) { - const double delta = (m == n) ? 1.0 : 0.0; - const double contrib = - 3.0 * x_prime(m) * x_prime(n) - delta * r_sq; + const double delta = (m == n) ? 1.0 : 0.0; + const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq; local_Q(m, n) += rho_val * contrib * weight; } } @@ -260,10 +227,7 @@ namespace mean_field::physics { } mfem::DenseMatrix global_Q(dim, dim); - MPI_Allreduce( - local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, - MPI_SUM, fem.mesh->GetComm() - ); + MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()); return global_Q; } @@ -289,8 +253,7 @@ namespace mean_field::physics { } } - const double l2_contrib = - -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor; + const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor; const double l0_contrib = -utils::G * total_mass / r; @@ -304,92 +267,69 @@ namespace mean_field::physics { // ========================================== // 1. Partially Assemble the High-Order Mass Block // ========================================== - f.gravityContext.m_form = - std::make_unique(f.gravityFluxFes.get()); + f.gravityContext.m_form = std::make_unique(f.gravityFluxFes.get()); f.gravityContext.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); std::unique_ptr hdiv_mass_integrator; if (f.has_mapping()) { f.gravityContext.mapped_hdiv_mass_coeff = - std::make_unique( - *f.mapping, f.mesh->Dimension() - ); + std::make_unique(*f.mapping, f.mesh->Dimension()); hdiv_mass_integrator = - std::make_unique( - *f.gravityContext.mapped_hdiv_mass_coeff - ); + std::make_unique(*f.gravityContext.mapped_hdiv_mass_coeff); } else { f.gravityContext.mapped_hdiv_mass_coeff.reset(); - hdiv_mass_integrator = - std::make_unique(); + hdiv_mass_integrator = std::make_unique(); } - const mfem::FiniteElement &hdiv_element = - *f.gravityFluxFes->GetTypicalFE(); - const mfem::ElementTransformation &hdiv_transformation = - *f.mesh->GetElementTransformation(0); + const mfem::FiniteElement &hdiv_element = *f.gravityFluxFes->GetTypicalFE(); + const mfem::ElementTransformation &hdiv_transformation = *f.mesh->GetElementTransformation(0); const quadrature::MappingKind mapping_kind = - f.has_mapping() ? quadrature::MappingKind::general - : quadrature::MappingKind::none; + f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none; f.quadratureFactory->configure_gravity_hdiv_mass( - *hdiv_mass_integrator, quadrature::QuadratureRole::discretization, - hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind - ); - f.gravityContext.m_form->AddDomainIntegrator( - hdiv_mass_integrator.release() + *hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation, + utils::DOMAINS::ALL, mapping_kind ); + f.gravityContext.m_form->AddDomainIntegrator(hdiv_mass_integrator.release()); f.gravityContext.m_form->Assemble(); // ========================================== // 2. Partially Assemble the High-Order Divergence Block // ========================================== - f.gravityContext.b_form = std::make_unique( - f.gravityFluxFes.get(), f.gravityPotentialFes.get() - ); + f.gravityContext.b_form = + std::make_unique(f.gravityFluxFes.get(), f.gravityPotentialFes.get()); f.gravityContext.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); - auto divergence_discretization_integrator = - std::make_unique(); - const mfem::FiniteElement &divergence_discretization_test_element = - *f.gravityPotentialFes->GetTypicalFE(); + auto divergence_discretization_integrator = std::make_unique(); + const mfem::FiniteElement &divergence_discretization_test_element = *f.gravityPotentialFes->GetTypicalFE(); f.quadratureFactory->configure_gravity_divergence( - *divergence_discretization_integrator, - quadrature::QuadratureRole::discretization, hdiv_element, - divergence_discretization_test_element, hdiv_transformation, - utils::DOMAINS::ALL, quadrature::MappingKind::none - ); - f.gravityContext.b_form->AddDomainIntegrator( - divergence_discretization_integrator.release() + *divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element, + divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL, + quadrature::MappingKind::none ); + f.gravityContext.b_form->AddDomainIntegrator(divergence_discretization_integrator.release()); f.gravityContext.b_form->Assemble(); MFEM_VERIFY( - f.domainMapperStateless != nullptr, - "Gravity source partial assembly requires the stateless domain " - "mapper." + f.domainMapperStateless != nullptr, "Gravity source partial assembly requires the stateless domain " + "mapper." ); mfem::Vector displacement_true(f.displacementFes->GetTrueVSize()); - displacement_true = 0.0; + displacement_true = 0.0; - const mfem::GridFunction *active_displacement = - f.mapping->GetDisplacement(); + const mfem::GridFunction *active_displacement = f.mapping->GetDisplacement(); if (active_displacement != nullptr) { - grid_function_to_true_dofs( - *f.displacementFes, *active_displacement, displacement_true - ); + grid_function_to_true_dofs(*f.displacementFes, *active_displacement, displacement_true); } auto source_form = - std::make_unique( - f, *f.domainMapperStateless - ); + std::make_unique(f, *f.domainMapperStateless); source_form->Prepare(displacement_true); @@ -397,12 +337,9 @@ namespace mean_field::physics { // ========================================== // 3. Assemble Global Block Operator // ========================================== - f.gravityContext.BT = std::make_unique( - f.gravityContext.b_form.get() - ); + f.gravityContext.BT = std::make_unique(f.gravityContext.b_form.get()); - f.gravityContext.block_A = - std::make_unique(f.gravityBlockTrueOffsets); + f.gravityContext.block_A = std::make_unique(f.gravityBlockTrueOffsets); f.gravityContext.block_A->SetBlock(0, 0, f.gravityContext.m_form.get()); f.gravityContext.block_A->SetBlock(0, 1, f.gravityContext.BT.get()); f.gravityContext.block_A->SetBlock(1, 0, f.gravityContext.b_form.get()); @@ -417,8 +354,7 @@ namespace mean_field::physics { for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) { MFEM_VERIFY( - std::isfinite(inverse_mass_diagonal(i)) && - inverse_mass_diagonal(i) > 0.0, + std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0, "Mapped RT mass matrix has a non-positive or non-finite " "diagonal " "entry." @@ -426,58 +362,34 @@ namespace mean_field::physics { inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i); } - mfem::ParMixedBilinearForm b_preconditioner( - f.gravityFluxFes.get(), f.gravityPotentialFes.get() - ); - auto divergence_preconditioner_integrator = - std::make_unique(); + mfem::ParMixedBilinearForm b_preconditioner(f.gravityFluxFes.get(), f.gravityPotentialFes.get()); + auto divergence_preconditioner_integrator = std::make_unique(); - const mfem::FiniteElement &divergence_trial_element = - *f.gravityFluxFes->GetTypicalFE(); - const mfem::FiniteElement &divergence_test_element = - *f.gravityPotentialFes->GetTypicalFE(); - const mfem::ElementTransformation &divergence_transformation = - *f.mesh->GetElementTransformation(0); + const mfem::FiniteElement &divergence_trial_element = *f.gravityFluxFes->GetTypicalFE(); + const mfem::FiniteElement &divergence_test_element = *f.gravityPotentialFes->GetTypicalFE(); + const mfem::ElementTransformation &divergence_transformation = *f.mesh->GetElementTransformation(0); f.quadratureFactory->configure_gravity_divergence( - *divergence_preconditioner_integrator, - quadrature::QuadratureRole::preconditioner, - divergence_trial_element, divergence_test_element, - divergence_transformation, utils::DOMAINS::ALL, - quadrature::MappingKind::none - ); - b_preconditioner.AddDomainIntegrator( - divergence_preconditioner_integrator.release() + *divergence_preconditioner_integrator, quadrature::QuadratureRole::preconditioner, divergence_trial_element, + divergence_test_element, divergence_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none ); + b_preconditioner.AddDomainIntegrator(divergence_preconditioner_integrator.release()); b_preconditioner.Assemble(); b_preconditioner.Finalize(); - std::unique_ptr b_matrix( - b_preconditioner.ParallelAssemble() - ); - std::unique_ptr inverse_mass_b_transpose( - b_matrix->Transpose() - ); + std::unique_ptr b_matrix(b_preconditioner.ParallelAssemble()); + std::unique_ptr inverse_mass_b_transpose(b_matrix->Transpose()); inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal); - f.gravityContext.Schur.reset( - mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get()) - ); + f.gravityContext.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get())); // ========================================== // 5. Wire Up the preconditioners // ========================================== - f.gravityContext.prec_M = - std::make_unique( - mass_diagonal, empty_tdofs - ); + f.gravityContext.prec_M = std::make_unique(mass_diagonal, empty_tdofs); f.gravityContext.prec_Phi->SetOperator(*f.gravityContext.Schur); - f.gravityContext.block_prec->SetDiagonalBlock( - 0, f.gravityContext.prec_M.get() - ); - f.gravityContext.block_prec->SetDiagonalBlock( - 1, f.gravityContext.prec_Phi.get() - ); + f.gravityContext.block_prec->SetDiagonalBlock(0, f.gravityContext.prec_M.get()); + f.gravityContext.block_prec->SetDiagonalBlock(1, f.gravityContext.prec_Phi.get()); } GravitySolution grav_potential_new( @@ -486,49 +398,32 @@ namespace mean_field::physics { const mfem::GridFunction &rho, const mfem::GridFunction &displacement ) { + MFEM_VERIFY(f.mesh != nullptr, "Gravity initialization requires a parallel mesh."); + MFEM_VERIFY(f.densityFes != nullptr, "Gravity initialization requires the density finite-element space."); MFEM_VERIFY( - f.mesh != nullptr, - "Gravity initialization requires a parallel mesh." + f.gravityPotentialFes != nullptr, "Gravity initialization requires the gravity-potential " + "finite-element " + "space." ); MFEM_VERIFY( - f.densityFes != nullptr, - "Gravity initialization requires the density finite-element space." - ); - MFEM_VERIFY( - f.gravityPotentialFes != nullptr, - "Gravity initialization requires the gravity-potential " - "finite-element " - "space." - ); - MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "Gravity initialization requires the " - "gravity-gradient finite-element space." + f.gravityFluxFes != nullptr, "Gravity initialization requires the " + "gravity-gradient finite-element space." ); MFEM_VERIFY( f.displacementFes != nullptr, "Gravity initialization requires the " "displacement finite-element space." ); + MFEM_VERIFY(f.domainMapperStateless != nullptr, "Gravity initialization requires the stateless domain mapper."); + MFEM_VERIFY(f.gravityContext.b_form != nullptr, "Gravity initialization requires the divergence operator."); MFEM_VERIFY( - f.domainMapperStateless != nullptr, - "Gravity initialization requires the stateless domain mapper." + f.gravityContext.BT != nullptr, "Gravity initialization requires the transpose divergence operator." ); MFEM_VERIFY( - f.gravityContext.b_form != nullptr, - "Gravity initialization requires the divergence operator." + f.gravityContext.block_prec != nullptr, "Gravity initialization requires the gravity block preconditioner." ); MFEM_VERIFY( - f.gravityContext.BT != nullptr, - "Gravity initialization requires the transpose divergence operator." - ); - MFEM_VERIFY( - f.gravityContext.block_prec != nullptr, - "Gravity initialization requires the gravity block preconditioner." - ); - MFEM_VERIFY( - rho.FESpace() == f.densityFes.get(), - "Gravity initialization requires density to use the FEM density " - "space." + rho.FESpace() == f.densityFes.get(), "Gravity initialization requires density to use the FEM density " + "space." ); MFEM_VERIFY( displacement.FESpace() == f.displacementFes.get(), @@ -540,53 +435,43 @@ namespace mean_field::physics { using form = utils::blocks::gravity_field_form; constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); const std::array value_sizes{ - f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), - f.gravityFluxFes->GetTrueVSize(), + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; const std::array residual_sizes{ - f.gravityFluxFes->GetTrueVSize(), - f.gravityPotentialFes->GetTrueVSize() + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; - const utils::blocks::form_layout layout( - value_sizes, residual_sizes - ); + const utils::blocks::form_layout layout(value_sizes, residual_sizes); mfem::Vector density_true; mfem::Vector displacement_true; grid_function_to_true_dofs(*f.densityFes, rho, density_true); - grid_function_to_true_dofs( - *f.displacementFes, displacement, displacement_true + grid_function_to_true_dofs(*f.displacementFes, displacement, displacement_true); + + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless ); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); - operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - operators::context::gravity_field::GravityFieldGeometryContext - reduced_geometry_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context( + f, *f.domainMapperStateless + ); operators::ReducedGravityFieldOperator reduced_operator( gravity_operator, reduced_geometry_context, displacement_true @@ -600,9 +485,7 @@ namespace mean_field::physics { "The reduced gravity right-hand side has the wrong size." ); - mfem::BlockVector gravity_state( - reduced_operator.GetGravityTrueOffsets() - ); + mfem::BlockVector gravity_state(reduced_operator.GetGravityTrueOffsets()); gravity_state = 0.0; mfem::MINRESSolver minres(f.mesh->GetComm()); @@ -614,20 +497,13 @@ namespace mean_field::physics { minres.SetPrintLevel(1); minres.Mult(right_hand_side, gravity_state); - MFEM_VERIFY( - minres.GetConverged(), - "The reduced gravity solve failed to converge." - ); + MFEM_VERIFY(minres.GetConverged(), "The reduced gravity solve failed to converge."); GravitySolution solution(f); - solution.gradPhi.SetFromTrueDofs( - gravity_state.GetBlock(gravity_gradient_residual_block) - ); + solution.gradPhi.SetFromTrueDofs(gravity_state.GetBlock(gravity_gradient_residual_block)); - solution.phi.SetFromTrueDofs( - gravity_state.GetBlock(gravity_poisson_residual_block) - ); + solution.phi.SetFromTrueDofs(gravity_state.GetBlock(gravity_poisson_residual_block)); return solution; } diff --git a/libmeanfield/impl/physics/solid.cpp b/libmeanfield/impl/physics/solid.cpp index 4a040a7..96f26bf 100644 --- a/libmeanfield/impl/physics/solid.cpp +++ b/libmeanfield/impl/physics/solid.cpp @@ -15,18 +15,13 @@ namespace mean_field::physics { if (fem.mesh->GetAttribute(i) == 3) continue; - mfem::ElementTransformation *T = - fem.mesh->GetElementTransformation(i); - using DensityField = field::Field; - const quadrature::Query query = - DensityField::make_query( - quadrature::QuadratureRole::diagnostic, T->OrderW(), - std::array{2}, utils::DOMAINS::STELLAR, - quadrature::MappingKind::general - ); - const mfem::IntegrationRule &ir = - *fem.quadratureFactory->get(query, T->GetGeometryType()) - .integration_rule; + mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i); + using DensityField = field::Field; + const quadrature::Query query = DensityField::make_query( + quadrature::QuadratureRole::diagnostic, T->OrderW(), std::array{2}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general + ); + const mfem::IntegrationRule &ir = *fem.quadratureFactory->get(query, T->GetGeometryType()).integration_rule; for (int j = 0; j < ir.GetNPoints(); j++) { const mfem::IntegrationPoint &ip = ir.IntPoint(j); @@ -37,19 +32,16 @@ namespace mean_field::physics { mfem::Vector x_phys; fem.mapping->GetPhysicalPoint(*T, ip, x_phys); - const double r_cyl_sq = - x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1); - const double detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip)); - const double weight = T->Weight() * ip.weight * detJ; + const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1); + const double detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip)); + const double weight = T->Weight() * ip.weight * detJ; local_I += rho_hat * r_cyl_sq * weight; } } double global_I = 0.0; - MPI_Allreduce( - &local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm() - ); + MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()); return global_I; } diff --git a/libmeanfield/impl/utils/domain.cpp b/libmeanfield/impl/utils/domain.cpp index 9b19862..3aa55bf 100644 --- a/libmeanfield/impl/utils/domain.cpp +++ b/libmeanfield/impl/utils/domain.cpp @@ -29,10 +29,8 @@ namespace mean_field::utils { mfem::Array origin_ip; fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false); - if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && - fem.mapping->HasDisplacementField()) { - mfem::ElementTransformation *T0 = - fem.mesh->GetElementTransformation(origin_elem[0]); + if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && fem.mapping->HasDisplacementField()) { + mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]); T0->SetIntPoint(&origin_ip[0]); mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim); @@ -98,8 +96,7 @@ namespace mean_field::utils { int elemID = elem_ids[0]; const mfem::IntegrationPoint &ip = ips[0]; - mfem::ElementTransformation *T = - fem.mesh->GetElementTransformation(elemID); + mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID); T->SetIntPoint(&ip); mfem::Vector current_x_phys(dim); @@ -160,8 +157,7 @@ namespace mean_field::utils { const mapping::COORDINATE_SPACE rspace ) { mfem::Vector x_search; - if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && - fem.has_mapping()) { + if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && fem.has_mapping()) { GetReferencePoint(fem, x, x_search); } else { x_search = x; @@ -177,8 +173,7 @@ namespace mean_field::utils { double local_val = 0.0; if (elem_ids.Size() > 0 && elem_ids[0] >= 0) { const double val = u.GetValue(elem_ids[0], ips[0]); - if (rspace == mapping::COORDINATE_SPACE::PHYSICAL && - !fem.has_mapping()) { + if (rspace == mapping::COORDINATE_SPACE::PHYSICAL && !fem.has_mapping()) { MFEM_ABORT( "Physical evaluation mode requested but no mapping " "provided. Check " @@ -189,9 +184,7 @@ namespace mean_field::utils { } double global_val = 0.0; - MPI_Allreduce( - &local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.mesh->GetComm() - ); + MPI_Allreduce(&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.mesh->GetComm()); return global_val; } diff --git a/libmeanfield/impl/utils/misc.cpp b/libmeanfield/impl/utils/misc.cpp index a341c49..ce3a766 100644 --- a/libmeanfield/impl/utils/misc.cpp +++ b/libmeanfield/impl/utils/misc.cpp @@ -10,18 +10,14 @@ namespace mean_field::utils { DOMAINS lhs, DOMAINS rhs ) { - return static_cast( - static_cast(lhs) | static_cast(rhs) - ); + return static_cast(static_cast(lhs) | static_cast(rhs)); } DOMAINS operator&( DOMAINS lhs, DOMAINS rhs ) { - return static_cast( - static_cast(lhs) & static_cast(rhs) - ); + return static_cast(static_cast(lhs) & static_cast(rhs)); } void populate_element_mask( @@ -37,8 +33,7 @@ namespace mean_field::utils { mask[0] = 1; } - if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && - max_attr >= 2) { + if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && max_attr >= 2) { mask[1] = 1; } @@ -92,10 +87,7 @@ namespace mean_field::utils { mesh->GetElementVertices(i, vertices); for (const int v : vertices) { const double *coords = mesh->GetVertex(v); - double r = std::sqrt( - coords[0] * coords[0] + coords[1] * coords[1] + - coords[2] * coords[2] - ); + double r = std::sqrt(coords[0] * coords[0] + coords[1] * coords[1] + coords[2] * coords[2]); local_min_r = std::min(local_min_r, r); local_max_r = std::max(local_max_r, r); } @@ -111,15 +103,9 @@ namespace mean_field::utils { comm = pmesh->GetComm(); } - MPI_Allreduce( - &local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm - ); - MPI_Allreduce( - &local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm - ); - MPI_Allreduce( - &l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm - ); + MPI_Allreduce(&local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm); + MPI_Allreduce(&local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm); + MPI_Allreduce(&l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm); if (global_found_vacuum) { return boundary::Bounds(global_min_r, global_max_r); diff --git a/libmeanfield/interface/analysis/integral.cppm b/libmeanfield/interface/analysis/integral.cppm index 1fd9726..d8e792e 100644 --- a/libmeanfield/interface/analysis/integral.cppm +++ b/libmeanfield/interface/analysis/integral.cppm @@ -11,9 +11,8 @@ export namespace mean_field::analysis { double domain_integrate_grid_function( const fem::FEM &fem, const mfem::GridFunction &gf, - utils::DOMAINS domain = utils::DOMAINS::ALL, - mapping::COORDINATE_SPACE coord_space = - mapping::COORDINATE_SPACE::PHYSICAL + utils::DOMAINS domain = utils::DOMAINS::ALL, + mapping::COORDINATE_SPACE coord_space = mapping::COORDINATE_SPACE::PHYSICAL ); mfem::Vector get_com( @@ -34,8 +33,7 @@ export namespace mean_field::analysis { double get_mesh_volume( const fem::FEM &fem, - mapping::COORDINATE_SPACE coordinate_space = - mapping::COORDINATE_SPACE::PHYSICAL, - utils::DOMAINS domain = utils::DOMAINS::STELLAR + mapping::COORDINATE_SPACE coordinate_space = mapping::COORDINATE_SPACE::PHYSICAL, + utils::DOMAINS domain = utils::DOMAINS::STELLAR ); } // namespace mean_field::analysis diff --git a/libmeanfield/interface/boundary/context.cppm b/libmeanfield/interface/boundary/context.cppm index c25c6ad..cc9957b 100644 --- a/libmeanfield/interface/boundary/context.cppm +++ b/libmeanfield/interface/boundary/context.cppm @@ -15,9 +15,7 @@ export namespace mean_field::boundary { Boundaries b, const int a ) { - return static_cast( - static_cast(b) - static_cast(a) - ); + return static_cast(static_cast(b) - static_cast(a)); } struct Bounds { diff --git a/libmeanfield/interface/eos/eos_base.cppm b/libmeanfield/interface/eos/eos_base.cppm new file mode 100644 index 0000000..9cd4d41 --- /dev/null +++ b/libmeanfield/interface/eos/eos_base.cppm @@ -0,0 +1,16 @@ +export module mean_field:eos.base; + +export namespace mean_field::eos { + class EquationOfState { + public: + virtual ~EquationOfState() = default; + [[nodiscard]] virtual double pressure_from_density(double density) const = 0; + [[nodiscard]] virtual double pressure_from_enthalpy(double enthalpy) const = 0; + [[nodiscard]] virtual double enthalpy_from_density(double density) const = 0; + [[nodiscard]] virtual double enthalpy_from_pressure(double pressure) const = 0; + [[nodiscard]] virtual double density_from_enthalpy(double enthalpy) const = 0; + [[nodiscard]] virtual double density_derivative_from_enthalpy(double enthalpy) const = 0; + [[nodiscard]] virtual double pressure_derivative_from_enthalpy(double enthalpy) const = 0; + [[nodiscard]] virtual double pressure_derivative_from_density(double density) const = 0; + }; +} // namespace mean_field::eos \ No newline at end of file diff --git a/libmeanfield/interface/eos/polytropic.cppm b/libmeanfield/interface/eos/polytropic.cppm new file mode 100644 index 0000000..1481b42 --- /dev/null +++ b/libmeanfield/interface/eos/polytropic.cppm @@ -0,0 +1,170 @@ +module; +#include +#include +#include +export module mean_field:eos.polytrope; +export import :eos.base; + +export namespace mean_field::eos { + class Polytrope final : public EquationOfState { + public: + Polytrope( + const double polytropic_index, + const double polytropic_constant + ) + : m_polytropic_index(polytropic_index), + m_polytropic_constant(polytropic_constant), + m_enthalpy_scale((polytropic_index + 1.0) * polytropic_constant) { + if (!std::isfinite(polytropic_index) || polytropic_index < 1.0) { + throw std::invalid_argument( + std::format( + "The differentiable polytropic closure requires a " + "finite polytropic index greater than or equal to one. " + "Instead a value of {} has been provided", + polytropic_index + ) + ); + } + + if (!std::isfinite(polytropic_constant) || polytropic_constant <= 0.0) { + throw std::invalid_argument( + std::format( + "The polytropic constant must be finite and positive. " + "Instead a value of {} has been provided", + polytropic_constant + ) + ); + } + }; + + [[nodiscard]] double polytropic_index() const noexcept { + return m_polytropic_index; + } + + [[nodiscard]] double polytropic_constant() const noexcept { + return m_polytropic_constant; + } + + [[nodiscard]] double enthalpy_scale() const noexcept { + return m_enthalpy_scale; + } + + [[nodiscard]] double pressure_from_density(const double density) const override { + validate_nonnegativity(density, "density"); + if (density == 0.0) { + return 0.0; + } + + return m_polytropic_constant * std::pow(density, 1.0 + 1.0 / m_polytropic_index); + } + + [[nodiscard]] double enthalpy_from_density(const double density) const override { + validate_nonnegativity(density, "density"); + if (density == 0.0) { + return 0.0; + } + + return m_enthalpy_scale * std::pow(density, 1.0 / m_polytropic_index); + } + + [[nodiscard]] double density_from_enthalpy(const double enthalpy) const override { + validate_finite(enthalpy, "enthalpy"); + + if (enthalpy <= 0.0) { + return 0.0; + } + + return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index); + } + + [[nodiscard]] double pressure_from_enthalpy(const double enthalpy) const override { + validate_finite(enthalpy, "enthalpy"); + + if (enthalpy <= 0.0) { + return 0.0; + } + + return density_from_enthalpy(enthalpy) * enthalpy / (m_polytropic_index + 1.0); + } + + [[nodiscard]] double density_derivative_from_enthalpy(const double enthalpy) const override { + validate_finite(enthalpy, "enthalpy"); + if (enthalpy < 0.0) { + return 0.0; + } + + if (enthalpy == 0.0) { + return m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0; + } + + return m_polytropic_index / m_enthalpy_scale * + std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0); + } + + [[nodiscard]] double pressure_derivative_from_enthalpy(const double enthalpy) const override { + validate_finite(enthalpy, "enthalpy"); + + if (enthalpy <= 0.0) { + return 0.0; + } + + return density_from_enthalpy(enthalpy); + } + + [[nodiscard]] double pressure_derivative_from_density(const double density) const override { + validate_nonnegativity(density, "density"); + if (density == 0.0) { + return 0.0; + } + + return m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) * + std::pow(density, 1.0 / m_polytropic_index); + } + + [[nodiscard]] double enthalpy_from_pressure(double pressure) const override { + validate_nonnegativity(pressure, "pressure"); + const double np1 = m_polytropic_index + 1; + return np1 * std::pow(m_polytropic_constant, m_polytropic_index / np1) * std::pow(pressure, 1.0 / np1); + } + + private: + static void validate_finite( + const double value, + const char *quantity + ) { + if (!std::isfinite(value)) { + throw std::domain_error( + std::format( + "The {} must be finite. Instead a value of {} has been " + "provided", + quantity, value + ) + ); + } + } + + static void validate_nonnegativity( + const double value, + const char *quantity + ) { + validate_finite(value, quantity); + if (value < 0.0) { + throw std::domain_error( + std::format( + "The {} must be non-negative. Instead a value of {} " + "has been " + "provided", + quantity, value + ) + ); + } + } + + public: + + private: + double m_polytropic_index; + double m_polytropic_constant; + double m_enthalpy_scale; + }; +} // namespace mean_field::eos \ No newline at end of file diff --git a/libmeanfield/interface/fem.cppm b/libmeanfield/interface/fem.cppm index 583d800..9f7abb6 100644 --- a/libmeanfield/interface/fem.cppm +++ b/libmeanfield/interface/fem.cppm @@ -148,26 +148,21 @@ export namespace mean_field::fem { [[nodiscard]] bool okay() const { return mesh != nullptr && - gravityPotentialFec != nullptr && - gravityPotentialFes != nullptr && - gravityFluxFec != nullptr && gravityFluxFes != nullptr && + gravityPotentialFec != nullptr && gravityPotentialFes != nullptr && gravityFluxFec != nullptr && + gravityFluxFes != nullptr && - displacementFec != nullptr && displacementFes != nullptr && - displacement != nullptr && + displacementFec != nullptr && displacementFes != nullptr && displacement != nullptr && densityFec != nullptr && densityFes != nullptr && enthalpyFec != nullptr && enthalpyFes != nullptr && - compactificationFec != nullptr && - compactificationFes != nullptr && + compactificationFec != nullptr && compactificationFes != nullptr && compactificationCoordinate != nullptr && - mapping != nullptr && domainMapperStateless != nullptr && - quadratureFactory != nullptr && + mapping != nullptr && domainMapperStateless != nullptr && quadratureFactory != nullptr && - blockTrueOffsets.Size() == 3 && - gravityBlockTrueOffsets.Size() == 3; + blockTrueOffsets.Size() == 3 && gravityBlockTrueOffsets.Size() == 3; } [[nodiscard]] bool has_mapping() const { diff --git a/libmeanfield/interface/field/field_base.cppm b/libmeanfield/interface/field/field_base.cppm index 343b459..7f35c8b 100644 --- a/libmeanfield/interface/field/field_base.cppm +++ b/libmeanfield/interface/field/field_base.cppm @@ -6,6 +6,7 @@ module; #include export module mean_field:field.base; +export import :utils.domain; export namespace mean_field::field { template struct TypeList { }; @@ -13,11 +14,9 @@ export namespace mean_field::field { template struct TypeListContains; template - struct TypeListContains> - : std::bool_constant<(std::same_as || ...)> { }; + struct TypeListContains> : std::bool_constant<(std::same_as || ...)> { }; - template - inline constexpr bool typeListContains = TypeListContains::value; + template inline constexpr bool typeListContains = TypeListContains::value; enum class StorageKind { finite_element, global_scalar }; @@ -44,14 +43,13 @@ export namespace mean_field::field { }; template - concept SpaceTag = std::same_as || std::same_as || - std::same_as || std::same_as; + concept SpaceTag = + std::same_as || std::same_as || std::same_as || std::same_as; template inline constexpr bool spaceSupportsRank = (std::same_as && (RankV == 0 || RankV == 1)) || - (std::same_as && (RankV == 0 || RankV == 1)) || - (std::same_as && RankV == 1) || + (std::same_as && (RankV == 0 || RankV == 1)) || (std::same_as && RankV == 1) || (std::same_as && RankV == 1); // ------------------------------------------------------------------------- @@ -105,51 +103,39 @@ export namespace mean_field::field { template struct IsCurl : std::false_type { }; - template - struct IsGradient> : std::true_type { }; + template struct IsGradient> : std::true_type { }; - template - struct IsDivergence> : std::true_type { - }; + template struct IsDivergence> : std::true_type { }; - template - struct IsCurl> : std::true_type { }; + template struct IsCurl> : std::true_type { }; template - concept ValidRelation = - std::same_as || - IsGradient::value || IsDivergence::value || - IsCurl::value; + concept ValidRelation = std::same_as || IsGradient::value || + IsDivergence::value || IsCurl::value; template struct RelationTarget { using Type = void; }; - template - struct RelationTarget> { + template struct RelationTarget> { using Type = SourceT; }; - template - struct RelationTarget> { + template struct RelationTarget> { using Type = SourceT; }; - template - struct RelationTarget> { + template struct RelationTarget> { using Type = SourceT; }; - template - using RelationTargetT = - typename RelationTarget::Type; + template using RelationTargetT = typename RelationTarget::Type; // ------------------------------------------------------------------------- // Field quantities // ------------------------------------------------------------------------- - template - struct Quantity { + template struct Quantity { using Relation = RelationT; using Discretization = DiscT; using Space = typename DiscT::Space; @@ -173,11 +159,9 @@ export namespace mean_field::field { ); }; - template - using ScalarQ = Quantity<0, RelationT, DiscT>; + template using ScalarQ = Quantity<0, RelationT, DiscT>; - template - using VectorQ = Quantity<1, RelationT, DiscT>; + template using VectorQ = Quantity<1, RelationT, DiscT>; struct GlobalScalarQ { using Relation = FieldRelation::Independent; @@ -188,73 +172,57 @@ export namespace mean_field::field { }; template - concept FieldQuantity = - requires { - typename T::Relation; - typename T::Discretization; - typename T::Space; + concept FieldQuantity = requires { + typename T::Relation; + typename T::Discretization; + typename T::Space; - { T::rankValue } -> std::convertible_to; - { T::familyOrder } -> std::convertible_to; - { T::storageKind } -> std::convertible_to; - { T::staticBlockSize } -> std::convertible_to; - } && SpaceTag && - T::storageKind == StorageKind::finite_element; + { T::rankValue } -> std::convertible_to; + { T::familyOrder } -> std::convertible_to; + { T::storageKind } -> std::convertible_to; + { T::staticBlockSize } -> std::convertible_to; + } && SpaceTag && T::storageKind == StorageKind::finite_element; template - concept GlobalScalarQuantity = - requires { - typename T::Relation; + concept GlobalScalarQuantity = requires { + typename T::Relation; - { T::rankValue } -> std::convertible_to; - { T::storageKind } -> std::convertible_to; - { T::staticBlockSize } -> std::convertible_to; - } && T::rankValue == 0 && - T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1; + { T::rankValue } -> std::convertible_to; + { T::storageKind } -> std::convertible_to; + { T::staticBlockSize } -> std::convertible_to; + } && T::rankValue == 0 && T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1; template concept RegisteredQuantity = FieldQuantity || GlobalScalarQuantity; template - concept DerivedQuantity = FieldQuantity && - (!std::same_as, void>); + concept DerivedQuantity = FieldQuantity && (!std::same_as, void>); // ------------------------------------------------------------------------- // Compile-time discretization constraints // ------------------------------------------------------------------------- - template - struct RtL2StablePair { + template struct RtL2StablePair { static consteval void validate() { static_assert( - std::same_as, - "The flux in an RT/L2 pair must use Raviart-Thomas elements." + std::same_as, "The flux in an RT/L2 pair must use Raviart-Thomas elements." ); static_assert( - std::same_as, - "The potential in an RT/L2 pair must use L2 elements." + std::same_as, "The potential in an RT/L2 pair must use L2 elements." ); - static_assert( - FluxT::rankValue == 1, - "The flux in an RT/L2 pair must be vector-valued." - ); + static_assert(FluxT::rankValue == 1, "The flux in an RT/L2 pair must be vector-valued."); + + static_assert(PotentialT::rankValue == 0, "The potential in an RT/L2 pair must be scalar-valued."); static_assert( - PotentialT::rankValue == 0, - "The potential in an RT/L2 pair must be scalar-valued." - ); - - static_assert( - FluxT::familyOrder == PotentialT::familyOrder, - "The MFEM RT and L2 family orders must match." + FluxT::familyOrder == PotentialT::familyOrder, "The MFEM RT and L2 family orders must match." ); } }; - template - consteval bool validate_constraints(TypeList) { + template consteval bool validate_constraints(TypeList) { (ConstraintTs::validate(), ...); return true; } @@ -276,16 +244,11 @@ export namespace mean_field::field { template concept FieldOperationTag = - std::same_as || - std::same_as || - std::same_as || - std::same_as || + std::same_as || std::same_as || + std::same_as || std::same_as || std::same_as; - template < - RegisteredQuantity QuantityT, - FieldOperationTag OperationT = FieldOperation::Value> - struct Operand { + template struct Operand { using Quantity = QuantityT; using Operation = OperationT; @@ -298,12 +261,10 @@ export namespace mean_field::field { }; template - concept FieldOperand = - requires { - typename T::Quantity; - typename T::Operation; - } && RegisteredQuantity && - FieldOperationTag; + concept FieldOperand = requires { + typename T::Quantity; + typename T::Operation; + } && RegisteredQuantity && FieldOperationTag; // ------------------------------------------------------------------------- // Weak-form descriptions @@ -315,11 +276,7 @@ export namespace mean_field::field { // coefficient supplied at runtime contributes one dynamic order. // ------------------------------------------------------------------------- - template < - auto PolicyKeyV, - std::size_t DynamicOrderCountV, - FieldOperand... OperandTs> - struct FormSpec { + template struct FormSpec { static constexpr auto policyKey = PolicyKeyV; static constexpr std::size_t dynamicOrderCount = DynamicOrderCountV; @@ -335,22 +292,46 @@ export namespace mean_field::field { { T::dynamicOrderCount } -> std::convertible_to; }; - template - struct IsRegisteredQuantityList : std::false_type { }; + template struct IsRegisteredQuantityList : std::false_type { }; template - struct IsRegisteredQuantityList> : std::true_type { - }; + struct IsRegisteredQuantityList> : std::true_type { }; - template - inline constexpr bool isRegisteredQuantityList = - IsRegisteredQuantityList::value; + template inline constexpr bool isRegisteredQuantityList = IsRegisteredQuantityList::value; template struct IsFieldFormList : std::false_type { }; - template - struct IsFieldFormList> : std::true_type { }; + template struct IsFieldFormList> : std::true_type { }; + + template inline constexpr bool isFieldFormList = IsFieldFormList::value; + + struct FieldSupport { }; + + template struct DomainSupport final : FieldSupport { + using Domain = DomainT; + }; + + struct NonSpatialSupport final : FieldSupport { }; + + template constexpr bool isDomainSupportV = false; + + template constexpr bool isDomainSupportV> = true; + + template + concept IsDomainSupport = isDomainSupportV; + + template + concept IsFieldSupport = std::derived_from; + + template using FieldSupportT = typename FieldT::Support; + + template + concept DomainSupportedField = requires { typename FieldT::Support; } && IsDomainSupport>; + + template + concept NonSpatialField = + requires { typename FieldT::Support; } && std::same_as, NonSpatialSupport>; + + template using FieldDomainT = typename FieldSupportT::Domain; - template - inline constexpr bool isFieldFormList = IsFieldFormList::value; } // namespace mean_field::field \ No newline at end of file diff --git a/libmeanfield/interface/field/field_mfem.cppm b/libmeanfield/interface/field/field_mfem.cppm index 06481eb..c08751e 100644 --- a/libmeanfield/interface/field/field_mfem.cppm +++ b/libmeanfield/interface/field/field_mfem.cppm @@ -26,9 +26,7 @@ namespace mean_field::field::detail { int familyOrder, int dimension ) { - return std::make_unique( - familyOrder, dimension - ); + return std::make_unique(familyOrder, dimension); } }; @@ -37,9 +35,7 @@ namespace mean_field::field::detail { int familyOrder, int dimension ) { - return std::make_unique( - familyOrder, dimension - ); + return std::make_unique(familyOrder, dimension); } }; @@ -48,9 +44,7 @@ namespace mean_field::field::detail { int familyOrder, int dimension ) { - return std::make_unique( - familyOrder, dimension - ); + return std::make_unique(familyOrder, dimension); } }; @@ -59,9 +53,7 @@ namespace mean_field::field::detail { int familyOrder, int dimension ) { - return std::make_unique( - familyOrder, dimension - ); + return std::make_unique(familyOrder, dimension); } }; @@ -86,8 +78,7 @@ namespace mean_field::field::detail { static constexpr int orderValue = []() consteval { if constexpr (GlobalScalarQuantity) { static_assert( - std::same_as, - "Global scalars support only the value operation." + std::same_as, "Global scalars support only the value operation." ); return 0; @@ -101,51 +92,36 @@ namespace mean_field::field::detail { } else { return familyOrder; } - } else if constexpr ( - std::same_as - ) { + } else if constexpr (std::same_as) { static_assert( - std::same_as, - "Only RT quantities currently support the divergence " - "polynomial-order rule." + std::same_as, "Only RT quantities currently support the divergence " + "polynomial-order rule." ); return familyOrder; - } else if constexpr ( - std::same_as - ) { + } else if constexpr (std::same_as) { static_assert( - std::same_as, - "Only H1 quantities currently support the gradient " - "polynomial-order rule." + std::same_as, "Only H1 quantities currently support the gradient " + "polynomial-order rule." ); return familyOrder > 0 ? familyOrder - 1 : 0; - } else if constexpr ( - std::same_as - ) { + } else if constexpr (std::same_as) { static_assert( - std::same_as, - "Only ND quantities currently support the curl " - "polynomial-order rule." + std::same_as, "Only ND quantities currently support the curl " + "polynomial-order rule." ); return familyOrder > 0 ? familyOrder - 1 : 0; - } else if constexpr ( - std::same_as - ) { + } else if constexpr (std::same_as) { static_assert( - std::same_as, - "Only RT quantities currently support the normal-trace " - "polynomial-order rule." + std::same_as, "Only RT quantities currently support the normal-trace " + "polynomial-order rule." ); return familyOrder; } else { - static_assert( - alwaysFalse, - "Unsupported MFEM field operation." - ); + static_assert(alwaysFalse, "Unsupported MFEM field operation."); } } }(); @@ -157,14 +133,9 @@ namespace mean_field::field::detail { template struct MfemFormOrder; - template < - auto PolicyKeyV, - std::size_t DynamicOrderCountV, - FieldOperand... OperandTs> - struct MfemFormOrder< - FormSpec> { - static constexpr int staticOrder = - (MfemOperandOrder::orderValue + ... + 0); + template + struct MfemFormOrder> { + static constexpr int staticOrder = (MfemOperandOrder::orderValue + ... + 0); }; // ------------------------------------------------------------------------- @@ -183,8 +154,7 @@ namespace mean_field::field::detail { if constexpr (QuantityT::rankValue == 0) { return 1; } else if constexpr ( - std::same_as || - std::same_as + std::same_as || std::same_as ) { return spaceDimension; } else { @@ -192,12 +162,10 @@ namespace mean_field::field::detail { } } - template - constexpr mfem::Ordering::Type get_ordering() { + template constexpr mfem::Ordering::Type get_ordering() { if constexpr ( QuantityT::rankValue == 1 && - (std::same_as || - std::same_as) + (std::same_as || std::same_as) ) { return mfem::Ordering::byVDIM; } else { @@ -213,40 +181,29 @@ namespace mean_field::field::detail { // ------------------------------------------------------------------------- template struct MfemQuantityTraits { - static std::unique_ptr - make_fec(int dimension) { - return FecFor::make( - QuantityT::familyOrder, dimension - ); + static std::unique_ptr make_fec(int dimension) { + return FecFor::make(QuantityT::familyOrder, dimension); } - static constexpr mfem::Ordering::Type ordering = - get_ordering(); + static constexpr mfem::Ordering::Type ordering = get_ordering(); }; template <> struct MfemQuantityTraits { - static std::unique_ptr - make_fec(int dimension) { + static std::unique_ptr make_fec(int dimension) { return std::make_unique( - Gravity::Flux::familyOrder, dimension, - mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL + Gravity::Flux::familyOrder, dimension, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL ); } - static constexpr mfem::Ordering::Type ordering = - mfem::Ordering::byNODES; + static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES; }; template <> struct MfemQuantityTraits { - static std::unique_ptr - make_fec(int dimension) { - return FecFor

::make( - Displacement::Vector::familyOrder, dimension - ); + static std::unique_ptr make_fec(int dimension) { + return FecFor

::make(Displacement::Vector::familyOrder, dimension); } - static constexpr mfem::Ordering::Type ordering = - mfem::Ordering::byNODES; + static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES; }; } // namespace mean_field::field::detail @@ -275,8 +232,7 @@ export namespace mean_field::field { requires typeListContains< QuantityT, typename TagT::Quantities> - static std::unique_ptr - make_fec(int dimension) { + static std::unique_ptr make_fec(int dimension) { if (dimension <= 0) { throw std::invalid_argument("Mesh dimension must be positive."); } @@ -299,8 +255,7 @@ export namespace mean_field::field { mfem::FiniteElementCollection &finiteElementCollection ) { return std::make_unique( - &mesh, &finiteElementCollection, - detail::get_vdim(mesh.SpaceDimension()), + &mesh, &finiteElementCollection, detail::get_vdim(mesh.SpaceDimension()), detail::MfemQuantityTraits::ordering ); } @@ -338,22 +293,17 @@ export namespace mean_field::field { int, FormT::dynamicOrderCount> dynamicOrders = {}, utils::DOMAINS domain = utils::DOMAINS::ALL, - quadrature::MappingKind mapping = quadrature::MappingKind::none + quadrature::MappingKind mapping = quadrature::MappingKind::none ) { if (geometryWeightOrder < 0) { - throw std::invalid_argument( - "Geometry weight order cannot be negative." - ); + throw std::invalid_argument("Geometry weight order cannot be negative."); } - int baseOrder = - detail::MfemFormOrder::staticOrder + geometryWeightOrder; + int baseOrder = detail::MfemFormOrder::staticOrder + geometryWeightOrder; for (const int dynamicOrder : dynamicOrders) { if (dynamicOrder < 0) { - throw std::invalid_argument( - "Dynamic polynomial orders cannot be negative." - ); + throw std::invalid_argument("Dynamic polynomial orders cannot be negative."); } baseOrder += dynamicOrder; @@ -377,4 +327,580 @@ export namespace mean_field::field { static_assert(FieldTag); static_assert(FieldTag); static_assert(FieldTag); + + /* + * Field-support realization onto MFEM element and DOF indices. + * + * A field's compile-time Support is declared in field.registry. + * These utilities resolve that semantic support through a DomainSchema + * onto a concrete MFEM finite-element space. + * + * Important: + * + * active DOFs = union of DOFs touched by supported elements + * + * This is deliberately NOT implemented as "remove every DOF touched by + * an unsupported element". For continuous spaces such as H1, a DOF on + * the Stellar/Vacuum interface is shared by elements on both sides and + * remains an active stellar-field DOF. + */ + + struct FieldLocalDofSupport { + /* + * Marker in local/vector-DOF numbering. + * + * Size == finiteElementSpace.GetVSize(). + * Entries are 1 for active DOFs and 0 otherwise. + */ + mfem::Array activeVDofMarker; + + /* + * Sorted MFEM local/vector DOF indices. + */ + mfem::Array activeVDofs; + mfem::Array inactiveVDofs; + }; + + struct FieldDofSupport { + /* + * Local/vector-DOF information. + * + * For a ParFiniteElementSpace the marker is synchronized across + * neighboring ranks before these lists are constructed, so a shared + * DOF is active on every rank carrying it if any rank has a supported + * element touching it. + */ + mfem::Array activeVDofMarker; + mfem::Array activeVDofs; + mfem::Array inactiveVDofs; + + /* + * True-DOF information owned by this MPI rank. + * + * Size of activeTrueDofMarker == GetTrueVSize(). + */ + mfem::Array activeTrueDofMarker; + mfem::Array activeTrueDofs; + mfem::Array inactiveTrueDofs; + }; + + template + concept MfemDomainField = FieldTag && DomainSupportedField; + + template < + MfemDomainField FieldT, + utils::domain::IsSchema SchemaT> + [[nodiscard]] + bool element_is_in_field_support( + const mfem::Mesh &mesh, + const int elementId + ) { + using DomainT = FieldDomainT; + + static_assert( + SchemaT::template contains_domain(), "The field support is not completely registered in the " + "supplied DomainSchema." + ); + + MFEM_VERIFY( + elementId >= 0 && elementId < mesh.GetNE(), "The requested field-support element ID is outside the mesh." + ); + + return SchemaT::template attribute_belongs_to(mesh.GetAttribute(elementId)); + } + + namespace detail { + inline void build_marker_lists( + const mfem::Array &activeMarker, + mfem::Array &activeDofs, + mfem::Array &inactiveDofs + ) { + mfem::FiniteElementSpace::MarkerToList(activeMarker, activeDofs); + + mfem::Array inactiveMarker(activeMarker.Size()); + + for (int dofId = 0; dofId < activeMarker.Size(); ++dofId) { + inactiveMarker[dofId] = activeMarker[dofId] == 0 ? 1 : 0; + } + + mfem::FiniteElementSpace::MarkerToList(inactiveMarker, inactiveDofs); + } + + template < + MfemDomainField FieldT, + utils::domain::IsSchema SchemaT> + [[nodiscard]] + mfem::Array build_local_active_vdof_marker(const mfem::FiniteElementSpace &finiteElementSpace) { + using DomainT = FieldDomainT; + + static_assert( + SchemaT::template contains_domain(), "The field support is not completely registered in the " + "supplied DomainSchema." + ); + + const mfem::Mesh *mesh = finiteElementSpace.GetMesh(); + + MFEM_VERIFY(mesh != nullptr, "Field-support DOF resolution requires an MFEM mesh."); + + MFEM_VERIFY( + finiteElementSpace.GetNE() == mesh->GetNE(), "The finite-element space and mesh have incompatible " + "element counts." + ); + + mfem::Array activeMarker(finiteElementSpace.GetVSize()); + + activeMarker = 0; + + mfem::Array elementVDofs; + + for (int elementId = 0; elementId < mesh->GetNE(); ++elementId) { + const int materialId = mesh->GetAttribute(elementId); + + if (!SchemaT::template attribute_belongs_to(materialId)) { + continue; + } + + finiteElementSpace.GetElementVDofs(elementId, elementVDofs); + + for (int localIndex = 0; localIndex < elementVDofs.Size(); ++localIndex) { + /* + * MFEM can encode orientation in a DOF index by using a + * negative value. DecodeDof removes that orientation sign + * and returns the actual local/vector DOF index. + */ + const int vdof = mfem::FiniteElementSpace::DecodeDof(elementVDofs[localIndex]); + + MFEM_VERIFY( + vdof >= 0 && vdof < finiteElementSpace.GetVSize(), + "MFEM returned an invalid element vector DOF." + ); + + activeMarker[vdof] = 1; + } + } + + return activeMarker; + } + } // namespace detail + + /* + * Serial/local support resolution. + * + * This works with any mfem::FiniteElementSpace and is particularly + * useful for topology/unit tests. + * + * The returned indices use MFEM local/vector-DOF numbering, not + * true-DOF numbering. + */ + template < + MfemDomainField FieldT, + utils::domain::IsSchema SchemaT> + [[nodiscard]] + FieldLocalDofSupport resolve_field_local_dof_support(const mfem::FiniteElementSpace &finiteElementSpace) { + FieldLocalDofSupport result; + + result.activeVDofMarker = detail::build_local_active_vdof_marker(finiteElementSpace); + + detail::build_marker_lists(result.activeVDofMarker, result.activeVDofs, result.inactiveVDofs); + + return result; + } + + /* + * Parallel production support resolution. + * + * This additionally converts the field support to the locally-owned + * true-DOF numbering used by nonlinear vectors and operators. + * + * For now this intentionally requires a conforming ParFiniteElementSpace. + * MFEM's nonconforming spaces require an additional constraint/conforming- + * DOF projection step; silently treating their local DOFs as ordinary + * true DOFs would be incorrect. + */ + template < + MfemDomainField FieldT, + utils::domain::IsSchema SchemaT> + [[nodiscard]] + FieldDofSupport resolve_field_dof_support(const mfem::ParFiniteElementSpace &finiteElementSpace) { + FieldDofSupport result; + + MFEM_VERIFY( + !finiteElementSpace.Nonconforming(), "Field-support true-DOF resolution currently requires a " + "conforming mfem::ParFiniteElementSpace." + ); + + result.activeVDofMarker = detail::build_local_active_vdof_marker(finiteElementSpace); + + /* + * Shared H1/RT DOFs can lie on an MPI partition boundary. + * + * If a supported element exists on one rank and the shared DOF also + * exists on a neighboring rank whose local elements are unsupported, + * that DOF must nevertheless be active globally. + * + * MFEM Synchronize performs the required OR-like synchronization of + * the marker across shared local DOFs. + */ + finiteElementSpace.Synchronize(result.activeVDofMarker); + + detail::build_marker_lists(result.activeVDofMarker, result.activeVDofs, result.inactiveVDofs); + + result.activeTrueDofMarker.SetSize(finiteElementSpace.GetTrueVSize()); + + result.activeTrueDofMarker = 0; + + for (int vdof = 0; vdof < result.activeVDofMarker.Size(); ++vdof) { + if (result.activeVDofMarker[vdof] == 0) { + continue; + } + + /* + * GetLocalTDofNumber returns the locally-owned true-DOF index + * for this local/vector DOF, or -1 when this rank does not own + * the shared true DOF. + * + * Because activeVDofMarker was synchronized first, the owning + * rank will also see the active marker. + */ + const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof); + + if (trueDof < 0) { + continue; + } + + MFEM_VERIFY(trueDof < result.activeTrueDofMarker.Size(), "MFEM returned an invalid local true DOF."); + + result.activeTrueDofMarker[trueDof] = 1; + } + + detail::build_marker_lists(result.activeTrueDofMarker, result.activeTrueDofs, result.inactiveTrueDofs); + + return result; + } + + /* + * Canonical correspondence between a dense reduced field vector and + * the selected MFEM true DOFs representing that field. + * + * The map contains no field, domain, mesh, or solver policy. It is an + * immutable indexing object once constructed: + * + * reduced index i + * | + * v + * reducedToTrue[i] + * | + * v + * MFEM true DOF + * + * trueToReduced supplies the inverse map. Unsupported true DOFs carry + * the sentinel -1. + * + * The reduced-to-true list is required to be strictly increasing. + * This makes reduced ordering deterministic and agrees with the + * canonical ordering produced by MFEM MarkerToList(). + */ + class FieldDofMap { + public: + FieldDofMap() = default; + + FieldDofMap( + const int fullTrueDofSize, + const mfem::Array &reducedToTrue + ) { + if (fullTrueDofSize < 0) { + throw std::invalid_argument("FieldDofMap requires a non-negative full true-DOF size."); + } + + m_fullTrueDofSize = fullTrueDofSize; + + m_reducedToTrue.SetSize(reducedToTrue.Size()); + + m_trueToReduced.SetSize(m_fullTrueDofSize); + + m_trueToReduced = -1; + + int previousTrueDof = -1; + + for (int reducedDof = 0; reducedDof < reducedToTrue.Size(); ++reducedDof) { + const int trueDof = reducedToTrue[reducedDof]; + + if (trueDof < 0 || trueDof >= m_fullTrueDofSize) { + throw std::invalid_argument( + "FieldDofMap contains a true DOF outside the full " + "true-DOF space." + ); + } + + if (reducedDof > 0 && trueDof <= previousTrueDof) { + throw std::invalid_argument( + "FieldDofMap reduced-to-true indices must be " + "strictly increasing and unique." + ); + } + + m_reducedToTrue[reducedDof] = trueDof; + + m_trueToReduced[trueDof] = reducedDof; + + previousTrueDof = trueDof; + } + } + + /* + * Construct directly from the support result produced by + * resolve_field_dof_support(). + * + * The marker is checked against the active true-DOF list so that + * an internally inconsistent FieldDofSupport cannot silently + * produce a solver map. + */ + explicit FieldDofMap(const FieldDofSupport &support) + : FieldDofMap( + support.activeTrueDofMarker.Size(), + support.activeTrueDofs + ) { + for (int trueDof = 0; trueDof < m_fullTrueDofSize; ++trueDof) { + const bool markerSaysActive = support.activeTrueDofMarker[trueDof] != 0; + + const bool mapSaysActive = m_trueToReduced[trueDof] >= 0; + + if (markerSaysActive != mapSaysActive) { + throw std::invalid_argument( + "FieldDofSupport active marker and active true-DOF " + "list are inconsistent." + ); + } + } + } + + [[nodiscard]] + int full_size() const noexcept { + return m_fullTrueDofSize; + } + + [[nodiscard]] + int reduced_size() const noexcept { + return m_reducedToTrue.Size(); + } + + [[nodiscard]] + int inactive_size() const noexcept { + return full_size() - reduced_size(); + } + + /* + * Because reducedToTrue is strictly increasing, a map containing + * every true DOF necessarily has + * + * reducedToTrue[i] == i. + */ + [[nodiscard]] + bool is_identity() const noexcept { + return reduced_size() == full_size(); + } + + [[nodiscard]] + const mfem::Array &reduced_to_true() const noexcept { + return m_reducedToTrue; + } + + /* + * Values are: + * + * >= 0 reduced DOF index + * -1 unsupported/inactive true DOF + */ + [[nodiscard]] + const mfem::Array &true_to_reduced() const noexcept { + return m_trueToReduced; + } + + [[nodiscard]] + bool contains_true_dof(const int trueDof) const { + validate_true_dof(trueDof); + + return m_trueToReduced[trueDof] >= 0; + } + + [[nodiscard]] + int true_dof(const int reducedDof) const { + if (reducedDof < 0 || reducedDof >= reduced_size()) { + throw std::out_of_range("Reduced DOF index is outside FieldDofMap."); + } + + return m_reducedToTrue[reducedDof]; + } + + [[nodiscard]] + std::optional reduced_dof(const int trueDof) const { + validate_true_dof(trueDof); + + const int reducedDof = m_trueToReduced[trueDof]; + + if (reducedDof < 0) { + return std::nullopt; + } + + return reducedDof; + } + + /* + * Gather: + * + * full MFEM true vector + * | + * v + * dense reduced solver vector + */ + void gather( + const mfem::Vector &full, + mfem::Vector &reduced + ) const { + require_full_size(full); + + require_reduced_size(reduced); + + for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) { + reduced(reducedDof) = full(m_reducedToTrue[reducedDof]); + } + } + + [[nodiscard]] + mfem::Vector gather(const mfem::Vector &full) const { + mfem::Vector reduced(reduced_size()); + + gather(full, reduced); + + return reduced; + } + + /* + * Scatter with projection semantics. + * + * All unsupported true DOFs are explicitly zeroed. + * + * This is the normal operation for constructing a complete MFEM + * representation of a supported field from the reduced nonlinear + * state. + * + * The output vector is NOT resized. This is intentional: callers + * may provide an mfem::Vector view into an mfem::BlockVector. + */ + void scatter( + const mfem::Vector &reduced, + mfem::Vector &full + ) const { + require_reduced_size(reduced); + + require_full_size(full); + + full = 0.0; + + scatter_into(reduced, full); + } + + [[nodiscard]] + mfem::Vector scatter(const mfem::Vector &reduced) const { + mfem::Vector full(full_size()); + + scatter(reduced, full); + + return full; + } + + /* + * Scatter while preserving unsupported values already present in + * the full vector. + * + * This is distinct from scatter() because future constrained field + * representations may need to preserve prescribed values outside + * the current reduced/free set. + */ + void scatter_into( + const mfem::Vector &reduced, + mfem::Vector &full + ) const { + require_reduced_size(reduced); + + require_full_size(full); + + for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) { + full(m_reducedToTrue[reducedDof]) = reduced(reducedDof); + } + } + + /* + * Add a reduced vector into the selected true DOFs. + * + * Unsupported true DOFs are untouched. + */ + void scatter_add( + const mfem::Vector &reduced, + mfem::Vector &full, + const double scale = 1.0 + ) const { + require_reduced_size(reduced); + + require_full_size(full); + + for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) { + full(m_reducedToTrue[reducedDof]) += scale * reduced(reducedDof); + } + } + + private: + void validate_true_dof(const int trueDof) const { + if (trueDof < 0 || trueDof >= full_size()) { + throw std::out_of_range("True DOF index is outside FieldDofMap."); + } + } + + void require_full_size(const mfem::Vector &vector) const { + if (vector.Size() != full_size()) { + throw std::invalid_argument("FieldDofMap full vector has an incompatible size."); + } + } + + void require_reduced_size(const mfem::Vector &vector) const { + if (vector.Size() != reduced_size()) { + throw std::invalid_argument("FieldDofMap reduced vector has an incompatible size."); + } + } + + int m_fullTrueDofSize{0}; + + /* + * Canonical forward mapping: + * + * reduced -> MFEM true + */ + mfem::Array m_reducedToTrue; + + /* + * Inverse mapping: + * + * MFEM true -> reduced + * + * Unsupported true DOFs are -1. + */ + mfem::Array m_trueToReduced; + }; + + /* + * Construct the canonical solver map for a registered spatial field. + * + * Field and domain semantics are used only while constructing the map. + * Consumers receive a plain FieldDofMap and therefore do not need to + * understand DomainSchema or field-support types. + */ + template < + MfemDomainField FieldT, + utils::domain::IsSchema SchemaT> + [[nodiscard]] + FieldDofMap make_field_dof_map(const mfem::ParFiniteElementSpace &finiteElementSpace) { + const FieldDofSupport support = resolve_field_dof_support(finiteElementSpace); + + return FieldDofMap(support); + } } // namespace mean_field::field diff --git a/libmeanfield/interface/field/field_registry.cppm b/libmeanfield/interface/field/field_registry.cppm index af36512..bfd5118 100644 --- a/libmeanfield/interface/field/field_registry.cppm +++ b/libmeanfield/interface/field/field_registry.cppm @@ -7,6 +7,7 @@ export module mean_field:field.registry; export import :field.base; export import :quadrature.policy; +export import :utils.domain; export namespace mean_field::field { // ========================================================================= @@ -17,70 +18,47 @@ export namespace mean_field::field { static constexpr std::string_view name = "density"; static constexpr int scalarOrder = 2; - struct Scalar final - : ScalarQ> { + using Support = DomainSupport; + + struct Scalar final : ScalarQ> { static constexpr std::string_view symbol = "ρ"; }; - using Quantities = TypeList; - using Constraints = TypeList<>; + using Quantities = TypeList; + using Constraints = TypeList<>; - static constexpr bool constraintsAreValid = - validate_constraints(Constraints{}); + static constexpr bool constraintsAreValid = validate_constraints(Constraints{}); static_assert(constraintsAreValid); struct Form { // Density-space mass matrix: (rho, q). - using ProjectionMass = FormSpec< - quadrature::Term::density_projection, - 0, - Operand, - Operand>; + using ProjectionMass = FormSpec, Operand>; // Projection RHS with one runtime coefficient order. - using ProjectionSource = FormSpec< - quadrature::Term::density_projection, - 1, - Operand>; + using ProjectionSource = FormSpec>; // Density-space contribution to the barotropic EOS closure: // (rho, q_rho). - using EosClosureMass = FormSpec< - quadrature::Term::eos_closure, - 0, - Operand, - Operand>; + using EosClosureMass = FormSpec, Operand>; // Integral of density over the physical volume. - using MassConservation = FormSpec< - quadrature::Term::mass_conservation, - 0, - Operand>; + using MassConservation = FormSpec>; // The same physical integral used as a nonlinear normalization // constraint. It has a distinct policy key so solver assembly and // diagnostics can be overintegrated independently. - using MassNormalization = FormSpec< - quadrature::Term::mass_normalization, - 0, - Operand>; + using MassNormalization = FormSpec>; // Integral of rho * x. The combined position-coefficient order is // supplied as one dynamic order. - using CenterOfMass = - FormSpec>; + using CenterOfMass = FormSpec>; // Integral of rho times the quadratic position tensor. The // combined tensor-coefficient order is supplied dynamically. - using Quadrupole = - FormSpec>; + using Quadrupole = FormSpec>; - using ErrorNorm = FormSpec< - quadrature::Term::error_norm, - 0, - Operand, - Operand>; + using ErrorNorm = FormSpec, Operand>; }; using FormList = TypeList< @@ -104,31 +82,26 @@ export namespace mean_field::field { static constexpr int potentialOrder = 2; static constexpr int fluxOrder = 2; - struct Potential final - : ScalarQ> { + using Support = DomainSupport; + + struct Potential final : ScalarQ> { static constexpr std::string_view symbol = "φ"; }; - struct Flux final - : VectorQ, Disc> { + struct Flux final : VectorQ, Disc> { static constexpr std::string_view symbol = "∇φ"; }; - using Quantities = TypeList; + using Quantities = TypeList; - using Constraints = TypeList>; + using Constraints = TypeList>; - static constexpr bool constraintsAreValid = - validate_constraints(Constraints{}); + static constexpr bool constraintsAreValid = validate_constraints(Constraints{}); static_assert(constraintsAreValid); struct Form { - using HDivMass = FormSpec< - quadrature::Term::gravity_hdiv_mass, - 0, - Operand, - Operand>; + using HDivMass = FormSpec, Operand>; using DivergenceCoupling = FormSpec< quadrature::Term::gravity_divergence, @@ -144,31 +117,18 @@ export namespace mean_field::field { // Density is a registered coefficient field and potential is the // test field, so the full polynomial order is compile-time data. - using SourceLinear = FormSpec< - quadrature::Term::gravity_source, - 0, - Operand, - Operand>; + using SourceLinear = + FormSpec, Operand>; // Mixed density-to-potential projection. Both trial and test // orders are registered quantities. - using SourceProjection = FormSpec< - quadrature::Term::gravity_source, - 0, - Operand, - Operand>; + using SourceProjection = + FormSpec, Operand>; - using PotentialErrorNorm = FormSpec< - quadrature::Term::error_norm, - 0, - Operand, - Operand>; + using PotentialErrorNorm = + FormSpec, Operand>; - using FluxErrorNorm = FormSpec< - quadrature::Term::error_norm, - 0, - Operand, - Operand>; + using FluxErrorNorm = FormSpec, Operand>; }; using FormList = TypeList< @@ -189,16 +149,16 @@ export namespace mean_field::field { static constexpr std::string_view name = "displacement"; static constexpr int vectorOrder = 3; - struct Vector final - : VectorQ> { + using Support = DomainSupport; + + struct Vector final : VectorQ> { static constexpr std::string_view symbol = "d"; }; - using Quantities = TypeList; - using Constraints = TypeList<>; + using Quantities = TypeList; + using Constraints = TypeList<>; - static constexpr bool constraintsAreValid = - validate_constraints(Constraints{}); + static constexpr bool constraintsAreValid = validate_constraints(Constraints{}); static_assert(constraintsAreValid); @@ -211,29 +171,50 @@ export namespace mean_field::field { Operand, Operand>; - using ErrorNorm = FormSpec< - quadrature::Term::error_norm, + // Positive gravitational contribution to the displacement row: + // + // int rho grad(phi) . w dV. + // + // Both the base geometry Jacobian and the displacement test + // function contribute to the polynomial order. The RT flux is + // mapped to physical space by the contravariant Piola map. + using GravityForce = FormSpec< + quadrature::Term::gravity_force, 0, - Operand, + Operand, + Operand, + Operand, Operand>; + + // Rigid-rotation contribution to the displacement row: + // + // -int rho grad(Psi_rotation) . w dV. + // + // grad(Psi_rotation) is linear in physical position, so its + // polynomial order is supplied as one runtime contribution. + using CentrifugalForce = + FormSpec, Operand>; + + using ErrorNorm = FormSpec, Operand>; }; - using FormList = TypeList; + using FormList = TypeList; }; struct BarotropicConstant { static constexpr std::string_view name = "barotropic_constant"; + using Support = NonSpatialSupport; + struct Scalar final : GlobalScalarQ { static constexpr std::string_view symbol = "C"; }; - using Quantities = TypeList; - using Constraints = TypeList<>; - using FormList = TypeList<>; + using Quantities = TypeList; + using Constraints = TypeList<>; + using FormList = TypeList<>; - static constexpr bool constraintsAreValid = - validate_constraints(Constraints{}); + static constexpr bool constraintsAreValid = validate_constraints(Constraints{}); static_assert(constraintsAreValid); }; @@ -250,16 +231,16 @@ export namespace mean_field::field { static constexpr std::string_view name = "specific_enthalpy"; static constexpr int scalarOrder = 3; - struct Scalar final - : ScalarQ> { + using Support = DomainSupport; + + struct Scalar final : ScalarQ> { static constexpr std::string_view symbol = "h"; }; - using Quantities = TypeList; - using Constraints = TypeList<>; + using Quantities = TypeList; + using Constraints = TypeList<>; - static constexpr bool constraintsAreValid = - validate_constraints(Constraints{}); + static constexpr bool constraintsAreValid = validate_constraints(Constraints{}); static_assert(constraintsAreValid); @@ -268,33 +249,21 @@ export namespace mean_field::field { // the extra polynomial order introduced by the nonlinear EOS // beyond the registered order of h. For an n=3 polytrope this is // 2 * hOrder, making rho(h) cubic in h. - using EosClosureSource = FormSpec< - quadrature::Term::eos_closure, - 1, - Operand, - Operand>; + using EosClosureSource = + FormSpec, Operand>; // (h, q_h) contribution to // h + phi - Psi_rotation - C = 0. - using EquilibriumEnthalpy = FormSpec< - quadrature::Term::hydrostatic_equilibrium, - 0, - Operand, - Operand>; + using EquilibriumEnthalpy = + FormSpec, Operand>; // (phi, q_h) contribution to hydrostatic equilibrium. - using EquilibriumGravity = FormSpec< - quadrature::Term::hydrostatic_equilibrium, - 0, - Operand, - Operand>; + using EquilibriumGravity = + FormSpec, Operand>; // (Psi_rotation, q_h). The rotation-potential order is supplied // dynamically because it belongs to runtime rotation data. - using EquilibriumRotation = FormSpec< - quadrature::Term::hydrostatic_equilibrium, - 1, - Operand>; + using EquilibriumRotation = FormSpec>; // (C, q_h), where C is spatially constant. using EquilibriumConstant = FormSpec< @@ -305,18 +274,11 @@ export namespace mean_field::field { // Boundary trace form available for weak enforcement, testing, or // a future multiplier formulation of h|Gamma_star = 0. - using IsobaricSurface = FormSpec< - quadrature::Term::isobaric_surface, - 0, - Operand, - Operand>; + using IsobaricSurface = FormSpec, Operand>; // Integral of P(h). The dynamic order is the extra EOS order // beyond the registered order of h. - using PressureIntegral = FormSpec< - quadrature::Term::pressure_integral, - 1, - Operand>; + using PressureIntegral = FormSpec>; // Weak pressure force in the displacement test space: // @@ -325,17 +287,13 @@ export namespace mean_field::field { // which is equivalent to -int P(h) div(w) dV. The dynamic order // is the extra EOS order beyond the registered order of h. For an // n=3 polytrope this is 3 * hOrder, making P(h) quartic in h. - using PressureForce = FormSpec< + using PressureForce = FormSpec< quadrature::Term::pressure_force, 1, Operand, Operand>; - using ErrorNorm = FormSpec< - quadrature::Term::error_norm, - 0, - Operand, - Operand>; + using ErrorNorm = FormSpec, Operand>; }; using FormList = TypeList< @@ -360,9 +318,10 @@ export namespace mean_field::field { typename T::Quantities; typename T::Constraints; typename T::FormList; + typename T::Support; { T::name } -> std::convertible_to; - } && isRegisteredQuantityList && + } && IsFieldSupport && isRegisteredQuantityList && isFieldFormList; static_assert(FieldTag); @@ -376,4 +335,22 @@ export namespace mean_field::field { static_assert(std::same_as< RelationTargetT, Gravity::Potential>); + + static_assert(std::same_as< + FieldDomainT, + utils::domain::Stellar>); + + static_assert(std::same_as< + FieldDomainT, + utils::domain::Stellar>); + + static_assert(std::same_as< + FieldDomainT, + utils::domain::All>); + + static_assert(std::same_as< + FieldDomainT, + utils::domain::All>); + + static_assert(NonSpatialField); } // namespace mean_field::field diff --git a/libmeanfield/interface/integrators/centrifugal.cppm b/libmeanfield/interface/integrators/centrifugal.cppm index a663250..2e7b1b6 100644 --- a/libmeanfield/interface/integrators/centrifugal.cppm +++ b/libmeanfield/interface/integrators/centrifugal.cppm @@ -4,8 +4,7 @@ export module mean_field:integrators.centrifugal; export import :mapping.domain_mapper; export namespace mean_field::integrators { - class CentrifugalForceIntegrator - : public mfem::BlockNonlinearFormIntegrator { + class CentrifugalForceIntegrator : public mfem::BlockNonlinearFormIntegrator { public: CentrifugalForceIntegrator( const mapping::DomainMapper &map, diff --git a/libmeanfield/interface/integrators/gravity.cppm b/libmeanfield/interface/integrators/gravity.cppm index 7a7f1a4..9b531c3 100644 --- a/libmeanfield/interface/integrators/gravity.cppm +++ b/libmeanfield/interface/integrators/gravity.cppm @@ -5,19 +5,13 @@ export module mean_field:integrators.gravity; import :mapping.domain_mapper; export namespace mean_field::integrators { - enum class GravityForceJacobianMode : std::uint8_t { - minimal, - field_coupled, - exact - }; + enum class GravityForceJacobianMode : std::uint8_t { minimal, field_coupled, exact }; - class GravityMomentumIntegrator - : public mfem::BlockNonlinearFormIntegrator { + class GravityMomentumIntegrator : public mfem::BlockNonlinearFormIntegrator { public: explicit GravityMomentumIntegrator( const mapping::DomainMapper &map, - GravityForceJacobianMode jacobian_mode = - GravityForceJacobianMode::field_coupled + GravityForceJacobianMode jacobian_mode = GravityForceJacobianMode::field_coupled ); void SetJacobianMode(GravityForceJacobianMode jacobian_mode); diff --git a/libmeanfield/interface/integrators/mass_continuity.cppm b/libmeanfield/interface/integrators/mass_continuity.cppm index 4c2e69f..28b98c2 100644 --- a/libmeanfield/interface/integrators/mass_continuity.cppm +++ b/libmeanfield/interface/integrators/mass_continuity.cppm @@ -4,8 +4,7 @@ export module mean_field:integrators.mass_continuity; import :mapping.domain_mapper; export namespace mean_field::integrators { - class ContinuityVolumeIntegrator - : public mfem::BlockNonlinearFormIntegrator { + class ContinuityVolumeIntegrator : public mfem::BlockNonlinearFormIntegrator { public: explicit ContinuityVolumeIntegrator(const mapping::DomainMapper &map); diff --git a/libmeanfield/interface/integrators/pressure_gradient.cppm b/libmeanfield/interface/integrators/pressure_gradient.cppm index 091adbb..89b7e37 100644 --- a/libmeanfield/interface/integrators/pressure_gradient.cppm +++ b/libmeanfield/interface/integrators/pressure_gradient.cppm @@ -7,9 +7,7 @@ import :mapping.domain_mapper; import :utils.misc; export namespace mean_field::integrators { - template - class PressureGradientIntegrator - : public mfem::BlockNonlinearFormIntegrator { + template class PressureGradientIntegrator : public mfem::BlockNonlinearFormIntegrator { public: PressureGradientIntegrator( const mapping::DomainMapper &map, @@ -74,8 +72,7 @@ export namespace mean_field::integrators { mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); mfem::Vector shape_rho(dof_rho); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { const mfem::IntegrationPoint &ip = ir->IntPoint(q); @@ -136,8 +133,7 @@ export namespace mean_field::integrators { mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim); mfem::Vector shape_rho(dof_rho); - const mfem::IntegrationRule *ir = - &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); + const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder()); for (int q = 0; q < ir->GetNPoints(); ++q) { using Scalar = EOS_T::value_type; @@ -168,8 +164,7 @@ export namespace mean_field::integrators { double debug_K = 1.5; double debug_n = 3.0; - double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) * - std::pow(xad::value(x_rho), 1.0 / debug_n); + double analytic_dp = debug_K * (1.0 + 1.0 / debug_n) * std::pow(xad::value(x_rho), 1.0 / debug_n); double ad_err = std::abs(dP_drho - analytic_dp); @@ -177,9 +172,8 @@ export namespace mean_field::integrators { for (int c = 0; c < dim; ++c) { int row = i + c * dof_v; for (int j = 0; j < dof_rho; ++j) { - int col = j; - double term = - dshape_v_phys(i, c) * dP_drho * shape_rho(j); + int col = j; + double term = dshape_v_phys(i, c) * dP_drho * shape_rho(j); (*dv_drho)(row, col) -= term * weight; } } diff --git a/libmeanfield/interface/mapping/compactification/kelvin.cppm b/libmeanfield/interface/mapping/compactification/kelvin.cppm index 6f02290..971e51a 100644 --- a/libmeanfield/interface/mapping/compactification/kelvin.cppm +++ b/libmeanfield/interface/mapping/compactification/kelvin.cppm @@ -10,9 +10,7 @@ export namespace mean_field::mapping::compactification { class KelvinCompactification final : public ExteriorDomainMap { public: - explicit KelvinCompactification( - options::KelvinCompactificationOptions options - ); + explicit KelvinCompactification(options::KelvinCompactificationOptions options); [[nodiscard]] MappingStatus Evaluate( const ExteriorMapInput &input, diff --git a/libmeanfield/interface/mapping/domain_mapper.cppm b/libmeanfield/interface/mapping/domain_mapper.cppm index ce221a2..cc82423 100644 --- a/libmeanfield/interface/mapping/domain_mapper.cppm +++ b/libmeanfield/interface/mapping/domain_mapper.cppm @@ -36,8 +36,7 @@ export namespace mean_field::mapping { mfem::Vector coordinate_gradient; }; - [[nodiscard]] ElementDisplacementData - ElementDisplacementDataFromElementVDofs( + [[nodiscard]] ElementDisplacementData ElementDisplacementDataFromElementVDofs( const mfem::FiniteElement &element, const mfem::Vector &displacement_dofs ); @@ -102,15 +101,13 @@ export namespace mean_field::mapping { public: DomainMapperStateless( utils::DomainMapperStatelessOptions options, - std::unique_ptr - exterior_map + std::unique_ptr exterior_map ); - DomainMapperStateless(const DomainMapperStateless &) = delete; - DomainMapperStateless & - operator=(const DomainMapperStateless &) = delete; - DomainMapperStateless(DomainMapperStateless &&) = default; - DomainMapperStateless &operator=(DomainMapperStateless &&) = default; + DomainMapperStateless(const DomainMapperStateless &) = delete; + DomainMapperStateless &operator=(const DomainMapperStateless &) = delete; + DomainMapperStateless(DomainMapperStateless &&) = default; + DomainMapperStateless &operator=(DomainMapperStateless &&) = default; [[nodiscard]] MappingStatus EvaluatePoint( const ElementMappingData &element_data, @@ -168,13 +165,10 @@ export namespace mean_field::mapping { FaceMappingVariation &variation ) const; - [[nodiscard]] bool IsCompactifiedElement( - const mfem::ElementTransformation &transformation - ) const noexcept; + [[nodiscard]] bool IsCompactifiedElement(const mfem::ElementTransformation &transformation) const noexcept; [[nodiscard]] int GetDimension() const noexcept; [[nodiscard]] int GetVacuumElementAttribute() const noexcept; - [[nodiscard]] const compactification::ExteriorDomainMap & - GetExteriorMap() const noexcept; + [[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept; private: void ValidateElementData(const ElementMappingData &element_data) const; @@ -196,21 +190,18 @@ export namespace mean_field::mapping { CompactificationPointData &point_data ) const; - [[nodiscard]] static mfem::ElementTransformation & - SelectFaceElementTransformation( + [[nodiscard]] static mfem::ElementTransformation &SelectFaceElementTransformation( mfem::FaceElementTransformations &transformation, FaceElementSide side ); - [[nodiscard]] static const mfem::IntegrationPoint & - SelectFaceElementIntegrationPoint( + [[nodiscard]] static const mfem::IntegrationPoint &SelectFaceElementIntegrationPoint( mfem::FaceElementTransformations &transformation, FaceElementSide side ); utils::DomainMapperStatelessOptions m_options; - std::unique_ptr - m_exterior_map; + std::unique_ptr m_exterior_map; }; class DomainMapper { @@ -226,8 +217,7 @@ export namespace mean_field::mapping { const double r_inf_ref ); - [[nodiscard]] bool - is_vacuum(const mfem::ElementTransformation &T) const; + [[nodiscard]] bool is_vacuum(const mfem::ElementTransformation &T) const; void SetDisplacement(const mfem::GridFunction &d); diff --git a/libmeanfield/interface/mean_field.cppm b/libmeanfield/interface/mean_field.cppm index 8693e87..36be90c 100644 --- a/libmeanfield/interface/mean_field.cppm +++ b/libmeanfield/interface/mean_field.cppm @@ -45,3 +45,21 @@ export import :operators.kernels.hydrostatic_equilibrium; export import :operators.context.hydrostatic_equilibrium; export import :operators.prepared_hydrostatic_equilibrium; export import :operators.kernels.pressure_force; +export import :operators.context.pressure_force; +export import :operators.prepared_pressure_force; +export import :operators.kernels.gravity_displacement_force; +export import :operators.prepared_gravity_displacement_force; +export import :operators.context.rotational_displacement_force; +export import :operators.kernels.rotational_displacement_force; +export import :operators.prepared_rotational_displacement_force; +export import :operators.prepared_displacement_residual; +export import :model.structure_profile; +export import :model.structure.base; +export import :model.structure.polytropic; +export import :eos.base; +export import :eos.polytrope; +export import :surface.base; +export import :surface.isobaric; +export import :model.stellar; +export import :operators.prepared_mass_normalization; +export import :operators.prepared_stellar_equilibrium; diff --git a/libmeanfield/interface/models/stellar_model.cppm b/libmeanfield/interface/models/stellar_model.cppm new file mode 100644 index 0000000..6a277fd --- /dev/null +++ b/libmeanfield/interface/models/stellar_model.cppm @@ -0,0 +1,114 @@ +module; + +#include +#include +#include +#include + +export module mean_field:model.stellar; + +export import :eos.base; +export import :model.structure.base; +export import :surface.base; + +export namespace mean_field::models { + template + concept StructurePrescription = + std::derived_from, mean_field::models::structure::StructureBase>; + + template + concept SurfacePrescription = std::derived_from, mean_field::surface::SurfaceBase>; + + /* + * Public ownership facade for a physical structure prescription and its + * stellar-surface prescription. + * + * The concrete prescriptions are allocated once at construction. Their + * stable addresses allow future prepared operators and contexts to borrow + * references without making ownership part of the user-facing API. + */ + class StellarModel final { + public: + template < + StructurePrescription StructureType, + SurfacePrescription SurfaceType> + explicit StellarModel( + StructureType &&structurePrescription, + SurfaceType &&surfacePrescription + ) + : StellarModel( + std::make_unique>( + std::forward(structurePrescription) + ), + std::make_unique>(std::forward(surfacePrescription)) + ) { + } + + ~StellarModel() = default; + + StellarModel(const StellarModel &) = delete; + + StellarModel &operator=(const StellarModel &) = delete; + + StellarModel(StellarModel &&) noexcept = default; + + StellarModel &operator=(StellarModel &&) noexcept = default; + + [[nodiscard]] const mean_field::models::structure::StructureBase &structurePrescription() const noexcept { + return *m_structurePrescription; + } + + [[nodiscard]] const mean_field::surface::SurfaceBase &surfacePrescription() const noexcept { + return *m_surfacePrescription; + } + + [[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept { + return m_structurePrescription->equationOfState(); + } + + [[nodiscard]] double targetMass() const noexcept { + return m_structurePrescription->targetMass(); + } + + [[nodiscard]] mean_field::models::structure::StructureSeed + makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const { + return m_structurePrescription->makeInitialSeed(request); + } + + [[nodiscard]] const mean_field::surface::ResolvedSurfaceCondition &resolvedSurfaceCondition() const noexcept { + return m_resolvedSurfaceCondition; + } + + private: + explicit StellarModel( + std::unique_ptr structurePrescription, + std::unique_ptr surfacePrescription + ) + : m_structurePrescription(std::move(structurePrescription)), + m_surfacePrescription(std::move(surfacePrescription)), + m_resolvedSurfaceCondition(validateAndResolve( + *m_structurePrescription, + *m_surfacePrescription + )) { + } + + [[nodiscard]] static mean_field::surface::ResolvedSurfaceCondition validateAndResolve( + const mean_field::models::structure::StructureBase &structurePrescription, + const mean_field::surface::SurfaceBase &surfacePrescription + ) { + structurePrescription.validate(); + + const mean_field::eos::EquationOfState &equationOfState = structurePrescription.equationOfState(); + + surfacePrescription.validate(equationOfState); + + return surfacePrescription.resolve(equationOfState); + } + + std::unique_ptr m_structurePrescription; + + std::unique_ptr m_surfacePrescription; + + mean_field::surface::ResolvedSurfaceCondition m_resolvedSurfaceCondition; + }; +} // namespace mean_field::models diff --git a/libmeanfield/interface/models/structure/polytropic.cppm b/libmeanfield/interface/models/structure/polytropic.cppm new file mode 100644 index 0000000..2fe049f --- /dev/null +++ b/libmeanfield/interface/models/structure/polytropic.cppm @@ -0,0 +1,68 @@ +module; + +#include + +#include + +export module mean_field:model.structure.polytropic; + +export import :eos.polytrope; +export import :model.structure.base; + +import :utils.misc; + +export namespace mean_field::models::structure { + class PolytropicStructure final : public StructureBase { + public: + explicit PolytropicStructure( + eos::Polytrope equationOfState, + double targetMass + ); + + [[nodiscard]] const eos::EquationOfState &equationOfState() const noexcept override; + + [[nodiscard]] double targetMass() const noexcept override; + + [[nodiscard]] StructureSeed makeInitialSeed(const StructureSeedRequest &request) const override; + + void validate() const override; + + private: + struct LaneEmdenPoint { + double coordinate{0.0}; + double value{0.0}; + double derivative{0.0}; + }; + + struct LaneEmdenDerivative { + double value{0.0}; + double derivative{0.0}; + }; + + static void validateSeedRequest(const StructureSeedRequest &request); + + [[nodiscard]] static LaneEmdenDerivative evaluateLaneEmdenRhs( + double coordinate, + double value, + double derivative, + double polytropicIndex + ); + + [[nodiscard]] static LaneEmdenPoint takeLaneEmdenStep( + const LaneEmdenPoint &point, + double step, + double polytropicIndex + ); + + [[nodiscard]] static std::vector solveLaneEmden(double polytropicIndex); + + [[nodiscard]] static double interpolateLaneEmdenValue( + const std::vector &solution, + double coordinate, + std::size_t &lowerIndex + ); + + eos::Polytrope m_equationOfState; + double m_targetMass; + }; +} // namespace mean_field::models::structure diff --git a/libmeanfield/interface/models/structure/structure_base.cppm b/libmeanfield/interface/models/structure/structure_base.cppm new file mode 100644 index 0000000..58dcdfd --- /dev/null +++ b/libmeanfield/interface/models/structure/structure_base.cppm @@ -0,0 +1,37 @@ +module; +#include +export module mean_field:model.structure.base; +export import :eos.base; + +export namespace mean_field::models::structure { + struct StructureSeed { + mfem::Vector radius; + mfem::Vector density; + mfem::Vector enthalpy; + + double stellarRadius; + double centralDensity; + double centralEnthalpy; + }; + + struct StructureSeedRequest { + double centralDensity; + int radialSampleCount{512}; + }; + + class StructureBase { + public: + virtual ~StructureBase() = default; + + [[nodiscard]] virtual const eos::EquationOfState &equationOfState() const noexcept = 0; + + [[nodiscard]] virtual double targetMass() const noexcept = 0; + + [[nodiscard]] virtual StructureSeed makeInitialSeed(const StructureSeedRequest &request) const = 0; + + virtual void validate() const = 0; + + protected: + StructureBase() = default; + }; +} // namespace mean_field::models::structure \ No newline at end of file diff --git a/libmeanfield/interface/models/structure_profile.cppm b/libmeanfield/interface/models/structure_profile.cppm new file mode 100644 index 0000000..1cc9613 --- /dev/null +++ b/libmeanfield/interface/models/structure_profile.cppm @@ -0,0 +1,146 @@ +module; +#include + +export module mean_field:model.structure_profile; +export import :fem; + +export namespace mean_field::models { + constexpr double DEFAULT_COLATITUDE_DEGREES = 90; + constexpr double DEFAULT_LONGITUDE_DEGREES = 0; + + struct RadialDirection { + double coLatitudeDegrees{DEFAULT_COLATITUDE_DEGREES}; + double longitudeDegrees{DEFAULT_LONGITUDE_DEGREES}; + + [[nodiscard]] mfem::Vector toUnitCartesian() const; + }; + + struct RadialProfile { + double coLatitudeDegrees; + double longitudeDegrees; + + mfem::Vector radius; + mfem::Vector param; + }; + + struct SliceProfile { + double radius; + std::array normal; + + mfem::GridFunction param; + }; + + class StructureProfile { + public: + explicit StructureProfile( + const fem::FEM &fem, + mfem::Vector state + ); + + // Profiles + mfem::GridFunction pressureProfile(); + mfem::GridFunction densityProfile(); + mfem::GridFunction gravitationalPotentialProfile(); + mfem::GridFunction gravitationalFieldProfile(); + mfem::GridFunction enthalpyProfile(); + mfem::GridFunction entropyProfile(); + mfem::GridFunction temperatureProfile(); + mfem::GridFunction internalEnergyProfile(); + + // Local Evaluation + double pressureAt(const mfem::Vector &position); + double densityAt(const mfem::Vector &position); + double gravitationalPotentialAt(const mfem::Vector &position); + mfem::Vector gravitationalFieldAt(const mfem::Vector &position); + double enthalpyAt(const mfem::Vector &position); + double entropyAt(const mfem::Vector &position); + double temperatureAt(const mfem::Vector &position); + double internalEnergyAt(const mfem::Vector &position); + + // Radial helpers + mfem::Vector radius(RadialDirection direction = {}); + + RadialProfile radialPressureProfile(RadialDirection direction = {}); + RadialProfile radialDensityProfile(RadialDirection direction = {}); + RadialProfile radialGravitationalPotentialProfile(RadialDirection direction = {}); + RadialProfile radialGravitationalFieldProfile(RadialDirection direction = {}); + RadialProfile radialEnthalpyProfile(RadialDirection direction = {}); + RadialProfile radialEntropyProfile(RadialDirection direction = {}); + RadialProfile radialTemperatureProfile(RadialDirection direction = {}); + RadialProfile radialInternalEnergyProfile(RadialDirection direction = {}); + + // Ellipsoidal Slices + SliceProfile slicePressureProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceDensityProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceGravitationalPotentialProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceGravitationalFieldProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceEnthalpyProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceEntropyProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceTemperatureProfile( + double radius, + const std::array< + double, + 2> &normal + ); + SliceProfile sliceInternalEnergyProfile( + double radius, + const std::array< + double, + 2> &normal + ); + + // Integral Constraints + double totalMass(); + double virialRatio(); + + // Diagnostics + bool isBound(); + + // IO + void radialToCSV( + const std::string &filename, + RadialDirection direction = {} + ); + void radialToBIN( + const std::string &filename, + RadialDirection direction = {} + ); + void toBIN(const std::string &filename); + + private: + const fem::FEM &m_fem; + const mfem::Vector m_state; + }; + + StructureProfile StructureProfileFromBIN(const std::string &filename); +} // namespace mean_field::models \ No newline at end of file diff --git a/libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm b/libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm index dac5c2b..5f037b6 100644 --- a/libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm +++ b/libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm @@ -1,5 +1,6 @@ module; +#include #include #include @@ -7,18 +8,66 @@ module; export module mean_field:operators.context.barotropic_closure_linearization; export import :fem; +export import :field.mfem; export import :mapping.domain_mapper; -export import :operators.prepared_barotropic_closure; -export import :physics.barotrope; export namespace mean_field::operators::context::barotropic { - struct BarotropicClosureRevisions final { - std::uint64_t density = 0; - std::uint64_t enthalpy = 0; - std::uint64_t displacement = 0; + template struct DependencyStamp { + std::uint64_t identity{0}; + std::uint64_t revision{0}; - [[nodiscard]] bool - operator==(const BarotropicClosureRevisions &) const noexcept = default; + [[nodiscard]] constexpr bool CanFollow(const DependencyStamp &prepared) const noexcept { + return identity != prepared.identity || revision >= prepared.revision; + } + + constexpr auto operator<=>(const DependencyStamp &) const = default; + }; + + struct DiscretizationDependencyTag { }; + struct DensityDependencyTag { }; + struct EnthalpyDependencyTag { }; + struct DisplacementDependencyTag { }; + + using DiscretizationDependency = DependencyStamp; + using DensityDependency = DependencyStamp; + using EnthalpyDependency = DependencyStamp; + using DisplacementDependency = DependencyStamp; + + struct BarotropicClosureDependencies final { + DiscretizationDependency discretization; + DensityDependency density; + EnthalpyDependency enthalpy; + DisplacementDependency displacement; + + constexpr auto operator<=>(const BarotropicClosureDependencies &) const = default; + }; + + struct BarotropicClosureStateView final { + const mfem::Vector &density; + const mfem::Vector &enthalpy; + const mfem::Vector &displacement; + }; + + struct BarotropicClosurePreparationReport final { + bool preparedStaticDependencies{false}; + bool preparedGeometryState{false}; + bool preparedBaseState{false}; + + bool updatedDensity{false}; + bool updatedEnthalpy{false}; + bool updatedDisplacement{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return preparedStaticDependencies || preparedGeometryState || preparedBaseState; + } + }; + + struct BarotropicClosurePreparationStatistics final { + std::uint64_t staticPreparations{0}; + std::uint64_t geometryPreparations{0}; + std::uint64_t baseStatePreparations{0}; + + constexpr auto operator<=>(const BarotropicClosurePreparationStatistics &) const = default; }; class BarotropicClosureLinearizationContext final { @@ -26,51 +75,46 @@ export namespace mean_field::operators::context::barotropic { BarotropicClosureLinearizationContext( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope + const field::FieldDofMap &densityMap, + const field::FieldDofMap &enthalpyMap, + const field::FieldDofMap &displacementMap ); - void Prepare( - const mfem::Vector &baseDensityTrue, - const mfem::Vector &baseEnthalpyTrue, - const mfem::Vector &displacementTrue, - const BarotropicClosureRevisions &revisions + BarotropicClosureLinearizationContext(const BarotropicClosureLinearizationContext &) = delete; + BarotropicClosureLinearizationContext &operator=(const BarotropicClosureLinearizationContext &) = delete; + BarotropicClosureLinearizationContext(BarotropicClosureLinearizationContext &&) = delete; + BarotropicClosureLinearizationContext &operator=(BarotropicClosureLinearizationContext &&) = delete; + + BarotropicClosurePreparationReport Prepare( + const BarotropicClosureStateView &state, + const BarotropicClosureDependencies &dependencies ); [[nodiscard]] bool IsPrepared() const noexcept; + [[nodiscard]] bool MatchesDependencies(const BarotropicClosureDependencies &dependencies) const noexcept; + [[nodiscard]] const BarotropicClosureDependencies &GetDependencies() const; + [[nodiscard]] const BarotropicClosurePreparationStatistics &GetPreparationStatistics() const noexcept; - [[nodiscard]] bool MatchesRevisions( - const BarotropicClosureRevisions &revisions - ) const noexcept; - - [[nodiscard]] std::uint64_t GetPreparationCount() const noexcept; - - [[nodiscard]] const BarotropicClosureRevisions &GetRevisions() const; - - [[nodiscard]] const mfem::Vector &GetBaseDensityTrue() const; - - [[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const; - - [[nodiscard]] const mfem::Vector &GetDisplacementTrue() const; - - [[nodiscard]] - const PreparedBarotropicClosureOperator &GetOperator() const noexcept; - - void BuildResidual(mfem::Vector &residual) const; + [[nodiscard]] const mfem::Vector &GetBaseDensity() const; + [[nodiscard]] const mfem::Vector &GetBaseEnthalpy() const; + [[nodiscard]] const mfem::Vector &GetDisplacement() const; private: void VerifyPrepared() const; const fem::FEM &m_f; + const mapping::DomainMapperStateless &m_domainMapper; - PreparedBarotropicClosureOperator m_operator; + int m_densitySize{0}; + int m_enthalpySize{0}; + int m_displacementSize{0}; - mfem::Vector m_baseDensityTrue; - mfem::Vector m_baseEnthalpyTrue; - mfem::Vector m_displacementTrue; + mfem::Vector m_baseDensity; + mfem::Vector m_baseEnthalpy; + mfem::Vector m_displacement; - BarotropicClosureRevisions m_revisions; - - std::uint64_t m_preparationCount = 0; - bool m_isPrepared = false; + BarotropicClosureDependencies m_dependencies; + BarotropicClosurePreparationStatistics m_statistics; + bool m_isPrepared{false}; }; -} // namespace mean_field::operators::context::barotropic \ No newline at end of file +} // namespace mean_field::operators::context::barotropic diff --git a/libmeanfield/interface/operators/contexts/gravity_field_context.cppm b/libmeanfield/interface/operators/contexts/gravity_field_context.cppm index ca48092..62bef30 100644 --- a/libmeanfield/interface/operators/contexts/gravity_field_context.cppm +++ b/libmeanfield/interface/operators/contexts/gravity_field_context.cppm @@ -51,8 +51,8 @@ export namespace mean_field::operators::context::gravity_field { bool refreshed_variation_state{false}; [[nodiscard]] bool DidAnyWork() const noexcept { - return reconstructed_operators || rebuilt_mass_operator || - rebuilt_source_operator || refreshed_variation_state; + return reconstructed_operators || rebuilt_mass_operator || rebuilt_source_operator || + refreshed_variation_state; } }; @@ -63,13 +63,10 @@ export namespace mean_field::operators::context::gravity_field { const mapping::DomainMapperStateless &domain_mapper ); - GravityFieldGeometryContext(const GravityFieldGeometryContext &) = - delete; - GravityFieldGeometryContext & - operator=(const GravityFieldGeometryContext &) = delete; - GravityFieldGeometryContext(GravityFieldGeometryContext &&) = delete; - GravityFieldGeometryContext & - operator=(GravityFieldGeometryContext &&) = delete; + GravityFieldGeometryContext(const GravityFieldGeometryContext &) = delete; + GravityFieldGeometryContext &operator=(const GravityFieldGeometryContext &) = delete; + GravityFieldGeometryContext(GravityFieldGeometryContext &&) = delete; + GravityFieldGeometryContext &operator=(GravityFieldGeometryContext &&) = delete; GravityFieldGeometryPreparation Prepare( const mfem::Vector &displacement_true, @@ -77,15 +74,11 @@ export namespace mean_field::operators::context::gravity_field { DisplacementRevision displacement_revision ); - [[nodiscard]] const PreparedMappedHDivMassOperator & - GetMassOperator() const; - [[nodiscard]] const PreparedMappedGravitySourceOperator & - GetSourceOperator() const; + [[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const; + [[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const; [[nodiscard]] const mfem::Vector &GetDisplacement() const; - [[nodiscard]] DiscretizationRevision - GetDiscretizationRevision() const noexcept; - [[nodiscard]] DisplacementRevision - GetDisplacementRevision() const noexcept; + [[nodiscard]] DiscretizationRevision GetDiscretizationRevision() const noexcept; + [[nodiscard]] DisplacementRevision GetDisplacementRevision() const noexcept; [[nodiscard]] bool IsPrepared() const noexcept; private: @@ -109,8 +102,7 @@ export namespace mean_field::operators::context::gravity_field { bool updated_gravity_gradient{false}; [[nodiscard]] bool DidAnyWork() const noexcept { - return geometry.DidAnyWork() || updated_density || - updated_gravity_gradient; + return geometry.DidAnyWork() || updated_density || updated_gravity_gradient; } }; @@ -121,23 +113,17 @@ export namespace mean_field::operators::context::gravity_field { const mapping::DomainMapperStateless &domain_mapper ); - GravityFieldLinearizationContext( - const GravityFieldLinearizationContext & - ) = delete; - GravityFieldLinearizationContext & - operator=(const GravityFieldLinearizationContext &) = delete; - GravityFieldLinearizationContext(GravityFieldLinearizationContext &&) = - delete; - GravityFieldLinearizationContext & - operator=(GravityFieldLinearizationContext &&) = delete; + GravityFieldLinearizationContext(const GravityFieldLinearizationContext &) = delete; + GravityFieldLinearizationContext &operator=(const GravityFieldLinearizationContext &) = delete; + GravityFieldLinearizationContext(GravityFieldLinearizationContext &&) = delete; + GravityFieldLinearizationContext &operator=(GravityFieldLinearizationContext &&) = delete; GravityFieldPreparationReport Prepare( const GravityFieldStateView &state, const GravityFieldRevisions &revisions ); - [[nodiscard]] const GravityFieldGeometryContext & - GetGeometryContext() const; + [[nodiscard]] const GravityFieldGeometryContext &GetGeometryContext() const; [[nodiscard]] const mfem::Vector &GetDensity() const; [[nodiscard]] const mfem::Vector &GetGravityGradient() const; [[nodiscard]] const GravityFieldRevisions &GetRevisions() const; diff --git a/libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm b/libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm index f83b9da..4c4596e 100644 --- a/libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm +++ b/libmeanfield/interface/operators/contexts/hydrostatic_equilibrium_context.cppm @@ -15,10 +15,8 @@ export namespace mean_field::operators::context::hydrostatic { std::uint64_t identity{0}; std::uint64_t revision{0}; - [[nodiscard]] constexpr bool - CanFollow(const DependencyStamp &prepared) const noexcept { - return identity != prepared.identity || - revision >= prepared.revision; + [[nodiscard]] constexpr bool CanFollow(const DependencyStamp &prepared) const noexcept { + return identity != prepared.identity || revision >= prepared.revision; } constexpr auto operator<=>(const DependencyStamp &) const = default; @@ -31,20 +29,17 @@ export namespace mean_field::operators::context::hydrostatic { struct RotationDependencyTag { }; struct BernoulliConstantDependencyTag { }; - using DiscretizationDependency = - DependencyStamp; + using DiscretizationDependency = DependencyStamp; - using EnthalpyDependency = DependencyStamp; + using EnthalpyDependency = DependencyStamp; - using GravityPotentialDependency = - DependencyStamp; + using GravityPotentialDependency = DependencyStamp; - using DisplacementDependency = DependencyStamp; + using DisplacementDependency = DependencyStamp; - using RotationDependency = DependencyStamp; + using RotationDependency = DependencyStamp; - using BernoulliConstantDependency = - DependencyStamp; + using BernoulliConstantDependency = DependencyStamp; struct HydrostaticEquilibriumDependencies { DiscretizationDependency discretization; @@ -54,8 +49,7 @@ export namespace mean_field::operators::context::hydrostatic { RotationDependency rotation; BernoulliConstantDependency bernoulliConstant; - constexpr auto - operator<=>(const HydrostaticEquilibriumDependencies &) const = default; + constexpr auto operator<=>(const HydrostaticEquilibriumDependencies &) const = default; }; struct HydrostaticEquilibriumStateView { @@ -77,8 +71,8 @@ export namespace mean_field::operators::context::hydrostatic { bool updatedBernoulliConstant{false}; [[nodiscard]] bool DidAnyWork() const noexcept { - return preparedStaticDependencies || preparedGeometryState || - preparedRotationDependencies || preparedBaseState; + return preparedStaticDependencies || preparedGeometryState || preparedRotationDependencies || + preparedBaseState; } }; @@ -88,8 +82,7 @@ export namespace mean_field::operators::context::hydrostatic { std::uint64_t rotationPreparations{0}; std::uint64_t baseStatePreparations{0}; - constexpr auto - operator<=>(const HydrostaticPreparationStatistics &) const = default; + constexpr auto operator<=>(const HydrostaticPreparationStatistics &) const = default; }; class HydrostaticEquilibriumContext { @@ -99,17 +92,13 @@ export namespace mean_field::operators::context::hydrostatic { const mapping::DomainMapperStateless &domainMapper ); - HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) = - delete; + HydrostaticEquilibriumContext(const HydrostaticEquilibriumContext &) = delete; - HydrostaticEquilibriumContext & - operator=(const HydrostaticEquilibriumContext &) = delete; + HydrostaticEquilibriumContext &operator=(const HydrostaticEquilibriumContext &) = delete; - HydrostaticEquilibriumContext(HydrostaticEquilibriumContext &&) = - delete; + HydrostaticEquilibriumContext(HydrostaticEquilibriumContext &&) = delete; - HydrostaticEquilibriumContext & - operator=(HydrostaticEquilibriumContext &&) = delete; + HydrostaticEquilibriumContext &operator=(HydrostaticEquilibriumContext &&) = delete; HydrostaticPreparationReport Prepare( const HydrostaticEquilibriumStateView &state, @@ -118,15 +107,11 @@ export namespace mean_field::operators::context::hydrostatic { [[nodiscard]] bool IsPrepared() const noexcept; - [[nodiscard]] bool MatchesDependencies( - const HydrostaticEquilibriumDependencies &dependencies - ) const noexcept; + [[nodiscard]] bool MatchesDependencies(const HydrostaticEquilibriumDependencies &dependencies) const noexcept; - [[nodiscard]] const HydrostaticEquilibriumDependencies & - GetDependencies() const; + [[nodiscard]] const HydrostaticEquilibriumDependencies &GetDependencies() const; - [[nodiscard]] const HydrostaticPreparationStatistics & - GetPreparationStatistics() const noexcept; + [[nodiscard]] const HydrostaticPreparationStatistics &GetPreparationStatistics() const noexcept; [[nodiscard]] const mfem::Vector &GetBaseEnthalpyTrue() const; diff --git a/libmeanfield/interface/operators/contexts/pressure_force_context.cppm b/libmeanfield/interface/operators/contexts/pressure_force_context.cppm new file mode 100644 index 0000000..9309883 --- /dev/null +++ b/libmeanfield/interface/operators/contexts/pressure_force_context.cppm @@ -0,0 +1,128 @@ +module; + +#include +#include + +#include + +export module mean_field:operators.context.pressure_force; + +export import :fem; +export import :field.mfem; +export import :mapping.domain_mapper; + +export namespace mean_field::operators::context::pressure_force { + template struct DependencyStamp final { + std::uint64_t identity{0}; + std::uint64_t revision{0}; + + [[nodiscard]] constexpr bool CanFollow(const DependencyStamp &prepared) const noexcept { + return identity != prepared.identity || revision >= prepared.revision; + } + + constexpr auto operator<=>(const DependencyStamp &) const = default; + }; + + struct DiscretizationDependencyTag final { }; + struct EnthalpyDependencyTag final { }; + struct DisplacementDependencyTag final { }; + + using DiscretizationDependency = DependencyStamp; + + using EnthalpyDependency = DependencyStamp; + + using DisplacementDependency = DependencyStamp; + + struct PressureForceDependencies final { + DiscretizationDependency discretization; + EnthalpyDependency enthalpy; + DisplacementDependency displacement; + + constexpr auto operator<=>(const PressureForceDependencies &) const = default; + }; + + /* + * Frozen solver-facing state. + * + * Both vectors use their registered FieldDof coordinates. + * + * Under the current registry: + * + * enthalpy -> Stellar -> reduced + * displacement -> All -> identity/full + */ + struct PressureForceStateView final { + const mfem::Vector &enthalpy; + const mfem::Vector &displacement; + }; + + struct PressureForcePreparationReport final { + bool preparedStaticDependencies{false}; + bool preparedGeometryState{false}; + bool preparedMaterialState{false}; + + bool updatedEnthalpy{false}; + bool updatedDisplacement{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return preparedStaticDependencies || preparedGeometryState || preparedMaterialState; + } + }; + + struct PressureForcePreparationStatistics final { + std::uint64_t staticPreparations{0}; + std::uint64_t geometryPreparations{0}; + std::uint64_t materialPreparations{0}; + + constexpr auto operator<=>(const PressureForcePreparationStatistics &) const = default; + }; + + class PressureForceLinearizationContext final { + public: + PressureForceLinearizationContext( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const field::FieldDofMap &enthalpyMap, + const field::FieldDofMap &displacementMap + ); + + PressureForceLinearizationContext(const PressureForceLinearizationContext &) = delete; + + PressureForceLinearizationContext &operator=(const PressureForceLinearizationContext &) = delete; + + PressureForceLinearizationContext(PressureForceLinearizationContext &&) = delete; + + PressureForceLinearizationContext &operator=(PressureForceLinearizationContext &&) = delete; + + PressureForcePreparationReport Prepare( + const PressureForceStateView &state, + const PressureForceDependencies &dependencies + ); + + [[nodiscard]] bool IsPrepared() const noexcept; + + [[nodiscard]] bool MatchesDependencies(const PressureForceDependencies &dependencies) const noexcept; + + [[nodiscard]] const PressureForceDependencies &GetDependencies() const; + + [[nodiscard]] const PressureForcePreparationStatistics &GetPreparationStatistics() const noexcept; + + [[nodiscard]] const mfem::Vector &GetBaseEnthalpy() const; + + [[nodiscard]] const mfem::Vector &GetDisplacement() const; + + private: + void VerifyPrepared() const; + + int m_enthalpySize{0}; + int m_displacementSize{0}; + + mfem::Vector m_baseEnthalpy; + mfem::Vector m_displacement; + + PressureForcePreparationStatistics m_statistics; + PressureForceDependencies m_dependencies; + + bool m_isPrepared{false}; + }; +} // namespace mean_field::operators::context::pressure_force \ No newline at end of file diff --git a/libmeanfield/interface/operators/contexts/rotation_displacement_force_context.cppm b/libmeanfield/interface/operators/contexts/rotation_displacement_force_context.cppm new file mode 100644 index 0000000..85be92c --- /dev/null +++ b/libmeanfield/interface/operators/contexts/rotation_displacement_force_context.cppm @@ -0,0 +1,124 @@ +module; + +#include +#include + +#include + +export module mean_field:operators.context.rotational_displacement_force; + +export import :fem; +export import :mapping.domain_mapper; + +export namespace mean_field::operators::context::rotational_displacement_force { + template struct DependencyStamp final { + std::uint64_t identity{0}; + std::uint64_t revision{0}; + + [[nodiscard]] constexpr bool CanFollow(const DependencyStamp &prepared) const noexcept { + return identity != prepared.identity || revision >= prepared.revision; + } + + constexpr auto operator<=>(const DependencyStamp &) const = default; + }; + + struct DiscretizationDependencyTag final { }; + struct DensityDependencyTag final { }; + struct DisplacementDependencyTag final { }; + struct RotationDependencyTag final { }; + + using DiscretizationDependency = DependencyStamp; + + using DensityDependency = DependencyStamp; + + using DisplacementDependency = DependencyStamp; + + using RotationDependency = DependencyStamp; + + struct RotationalDisplacementForceDependencies final { + DiscretizationDependency discretization; + DensityDependency density; + DisplacementDependency displacement; + RotationDependency rotation; + + constexpr auto operator<=>(const RotationalDisplacementForceDependencies &) const = default; + }; + + struct RotationalDisplacementForceStateView final { + const mfem::Vector &density; + const mfem::Vector &displacement; + }; + + struct RotationalDisplacementForcePreparationReport final { + bool preparedStaticDependencies{false}; + bool preparedGeometryState{false}; + bool preparedRotationDependencies{false}; + bool preparedBaseState{false}; + + bool updatedDensity{false}; + bool updatedDisplacement{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return preparedStaticDependencies || preparedGeometryState || preparedRotationDependencies || + preparedBaseState; + } + }; + + struct RotationalDisplacementForcePreparationStatistics final { + std::uint64_t staticPreparations{0}; + std::uint64_t geometryPreparations{0}; + std::uint64_t rotationPreparations{0}; + std::uint64_t baseStatePreparations{0}; + + constexpr auto operator<=>(const RotationalDisplacementForcePreparationStatistics &) const = default; + }; + + class RotationalDisplacementForceLinearizationContext final { + public: + RotationalDisplacementForceLinearizationContext( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper + ); + + RotationalDisplacementForceLinearizationContext(const RotationalDisplacementForceLinearizationContext &) = + delete; + + RotationalDisplacementForceLinearizationContext & + operator=(const RotationalDisplacementForceLinearizationContext &) = delete; + + RotationalDisplacementForceLinearizationContext(RotationalDisplacementForceLinearizationContext &&) = delete; + + RotationalDisplacementForceLinearizationContext & + operator=(RotationalDisplacementForceLinearizationContext &&) = delete; + + RotationalDisplacementForcePreparationReport Prepare( + const RotationalDisplacementForceStateView &state, + const RotationalDisplacementForceDependencies &dependencies + ); + + [[nodiscard]] bool IsPrepared() const noexcept; + + [[nodiscard]] bool + MatchesDependencies(const RotationalDisplacementForceDependencies &dependencies) const noexcept; + + [[nodiscard]] const RotationalDisplacementForceDependencies &GetDependencies() const; + + [[nodiscard]] const RotationalDisplacementForcePreparationStatistics &GetPreparationStatistics() const noexcept; + + [[nodiscard]] const mfem::Vector &GetBaseDensityTrue() const; + + [[nodiscard]] const mfem::Vector &GetDisplacementTrue() const; + + private: + void VerifyPrepared() const; + + const fem::FEM &m_f; + + mfem::Vector m_baseDensityTrue; + mfem::Vector m_displacementTrue; + + RotationalDisplacementForceDependencies m_dependencies; + RotationalDisplacementForcePreparationStatistics m_statistics; + bool m_isPrepared{false}; + }; +} // namespace mean_field::operators::context::rotational_displacement_force diff --git a/libmeanfield/interface/operators/gravity_field.cppm b/libmeanfield/interface/operators/gravity_field.cppm index 09a44d6..98a668c 100644 --- a/libmeanfield/interface/operators/gravity_field.cppm +++ b/libmeanfield/interface/operators/gravity_field.cppm @@ -10,27 +10,20 @@ export import :operators.gravity_field_jacobian; export import :operators.context.gravity_field; export namespace mean_field::operators { - enum class GravityResidualBlock : std::uint8_t { - gradient_equation = 0, - poisson_equation = 1, - count = 2 - }; + enum class GravityResidualBlock : std::uint8_t { gradient_equation = 0, poisson_equation = 1, count = 2 }; - constexpr int - gravity_residual_block_index(const GravityResidualBlock block) noexcept { + constexpr int gravity_residual_block_index(const GravityResidualBlock block) noexcept { return static_cast(block); } - inline constexpr int gravity_residual_block_count = - gravity_residual_block_index(GravityResidualBlock::count); + inline constexpr int gravity_residual_block_count = gravity_residual_block_index(GravityResidualBlock::count); class GravityFieldOperator final : public mfem::Operator { public: GravityFieldOperator( fem::FEM &f, const mapping::DomainMapperStateless &domain_mapper, - context::gravity_field::GravityFieldLinearizationContext - &linearization_context, + context::gravity_field::GravityFieldLinearizationContext &linearization_context, const mfem::Array &state_true_offsets, GravityFieldJacobianOperator &jacobian ); @@ -47,39 +40,32 @@ export namespace mean_field::operators { Operator &GetGradient(const mfem::Vector &state) const override; - [[nodiscard]] const mfem::Array & - GetStateTrueOffsets() const noexcept; + [[nodiscard]] const mfem::Array &GetStateTrueOffsets() const noexcept; - [[nodiscard]] const mfem::Array & - GetResidualTrueOffsets() const noexcept; + [[nodiscard]] const mfem::Array &GetResidualTrueOffsets() const noexcept; - [[nodiscard]] context::gravity_field::GravityFieldLinearizationContext & - GetLinearizationContext() noexcept; + [[nodiscard]] context::gravity_field::GravityFieldLinearizationContext &GetLinearizationContext() noexcept; - [[nodiscard]] const context::gravity_field:: - GravityFieldLinearizationContext & - GetLinearizationContext() const noexcept; + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetLinearizationContext() const noexcept; void ApplyGravityUnknowns( const mfem::Vector &gravity_gradient, const mfem::Vector &gravity_potential, - const context::gravity_field::GravityFieldGeometryContext - &geometry_context, + const context::gravity_field::GravityFieldGeometryContext &geometry_context, mfem::Vector &action ) const; void ApplyDensitySource( const mfem::Vector &density, - const context::gravity_field::GravityFieldGeometryContext - &geometry_context, + const context::gravity_field::GravityFieldGeometryContext &geometry_context, mfem::Vector &action ) const; private: fem::FEM &m_fem; const mapping::DomainMapperStateless &m_domain_mapper; - context::gravity_field::GravityFieldLinearizationContext - &m_linearization_context; + context::gravity_field::GravityFieldLinearizationContext &m_linearization_context; mfem::Array m_state_true_offsets; mfem::Array m_residual_true_offsets; GravityFieldJacobianOperator &m_jacobian; @@ -89,18 +75,14 @@ export namespace mean_field::operators { public: ReducedGravityFieldOperator( GravityFieldOperator &gravity_field_operator, - context::gravity_field::GravityFieldGeometryContext - &gravity_field_geometry_context, + context::gravity_field::GravityFieldGeometryContext &gravity_field_geometry_context, const mfem::Vector &displacement ); - ReducedGravityFieldOperator(const ReducedGravityFieldOperator &) = - delete; - ReducedGravityFieldOperator & - operator=(const ReducedGravityFieldOperator &) = delete; - ReducedGravityFieldOperator(ReducedGravityFieldOperator &&) = delete; - ReducedGravityFieldOperator & - operator=(ReducedGravityFieldOperator &&) = delete; + ReducedGravityFieldOperator(const ReducedGravityFieldOperator &) = delete; + ReducedGravityFieldOperator &operator=(const ReducedGravityFieldOperator &) = delete; + ReducedGravityFieldOperator(ReducedGravityFieldOperator &&) = delete; + ReducedGravityFieldOperator &operator=(ReducedGravityFieldOperator &&) = delete; void SetDisplacement(const mfem::Vector &displacement); @@ -118,18 +100,13 @@ export namespace mean_field::operators { [[nodiscard]] GravityFieldOperator &GetGravityFieldOperator() noexcept; - [[nodiscard]] const GravityFieldOperator & - GetGravityFieldOperator() const noexcept; + [[nodiscard]] const GravityFieldOperator &GetGravityFieldOperator() const noexcept; - [[nodiscard]] context::gravity_field::GravityFieldGeometryContext & - GetGeometryContext() noexcept; + [[nodiscard]] context::gravity_field::GravityFieldGeometryContext &GetGeometryContext() noexcept; - [[nodiscard]] const context::gravity_field:: - GravityFieldGeometryContext & - GetGeometryContext() const noexcept; + [[nodiscard]] const context::gravity_field::GravityFieldGeometryContext &GetGeometryContext() const noexcept; - [[nodiscard]] const mfem::Array & - GetGravityTrueOffsets() const noexcept; + [[nodiscard]] const mfem::Array &GetGravityTrueOffsets() const noexcept; private: void ValidateDisplacement(const mfem::Vector &displacement) const; @@ -141,7 +118,6 @@ export namespace mean_field::operators { private: GravityFieldOperator &m_gravity_field_operator; mfem::Array m_gravity_true_offsets; - context::gravity_field::GravityFieldGeometryContext - &m_gravity_field_geometry_context; + context::gravity_field::GravityFieldGeometryContext &m_gravity_field_geometry_context; }; } // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/interface/operators/gravity_field_jacobian.cppm b/libmeanfield/interface/operators/gravity_field_jacobian.cppm index 0bda980..44ed8a1 100644 --- a/libmeanfield/interface/operators/gravity_field_jacobian.cppm +++ b/libmeanfield/interface/operators/gravity_field_jacobian.cppm @@ -12,8 +12,7 @@ export namespace mean_field::operators { GravityFieldJacobianOperator( fem::FEM &f, const mapping::DomainMapperStateless &domain_mapper, - const context::gravity_field::GravityFieldLinearizationContext - &linearization_context, + const context::gravity_field::GravityFieldLinearizationContext &linearization_context, const mfem::Array &state_true_offsets, const mfem::Array &residual_true_offsets ); @@ -23,15 +22,13 @@ export namespace mean_field::operators { mfem::Vector &action ) const override; - [[nodiscard]] const context::gravity_field:: - GravityFieldLinearizationContext & - GetLinearizationContext() const noexcept; + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetLinearizationContext() const noexcept; private: fem::FEM &m_fem; const mapping::DomainMapperStateless &m_domain_mapper; - const context::gravity_field::GravityFieldLinearizationContext - &m_linearization_context; + const context::gravity_field::GravityFieldLinearizationContext &m_linearization_context; mfem::Array m_state_true_offsets; mfem::Array m_residual_true_offsets; }; diff --git a/libmeanfield/interface/operators/kernels/barotropic_closure_kernels.cppm b/libmeanfield/interface/operators/kernels/barotropic_closure_kernels.cppm index 722ea82..67cd172 100644 --- a/libmeanfield/interface/operators/kernels/barotropic_closure_kernels.cppm +++ b/libmeanfield/interface/operators/kernels/barotropic_closure_kernels.cppm @@ -4,15 +4,26 @@ module; export module mean_field:operators.kernels.barotropic_closure; +export import :eos.polytrope; export import :fem; export import :mapping.domain_mapper; -export import :physics.barotrope; export namespace mean_field::operators::kernels { + /* + * Stateless full-MFEM reference kernels for + * + * R_rho = \int_{Omega_star} (rho - rho_EOS(h)) q_rho dV. + * + * These functions intentionally remain expressed in complete MFEM true + * vectors. Solver/prepared-facing support reduction belongs to + * PreparedBarotropicClosureOperator through FieldDofMap. Keeping this + * layer full-space preserves an independent reference implementation for + * R K P tests of the reduced prepared operator. + */ void apply_barotropic_closure( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &equationOfState, const mfem::Vector &densityTrue, const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue, @@ -22,7 +33,7 @@ export namespace mean_field::operators::kernels { void apply_barotropic_closure_density_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &equationOfState, const mfem::Vector &densityVariationTrue, const mfem::Vector &displacementTrue, mfem::Vector &action @@ -31,7 +42,7 @@ export namespace mean_field::operators::kernels { void apply_barotropic_closure_enthalpy_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &equationOfState, const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &enthalpyVariationTrue, const mfem::Vector &displacementTrue, @@ -41,11 +52,11 @@ export namespace mean_field::operators::kernels { void apply_barotropic_closure_displacement_action( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &equationOfState, const mfem::Vector &baseDensityTrue, const mfem::Vector &baseEnthalpyTrue, const mfem::Vector &displacementTrue, const mfem::Vector &displacementVariationTrue, mfem::Vector &action ); -} // namespace mean_field::operators::kernels \ No newline at end of file +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm b/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm new file mode 100644 index 0000000..446496e --- /dev/null +++ b/libmeanfield/interface/operators/kernels/gravity_displacement_force_kernels.cppm @@ -0,0 +1,59 @@ +module; + +#include + +export module mean_field:operators.kernels.gravity_displacement_force; + +export import :fem; +export import :mapping.domain_mapper; + +export namespace mean_field::operators::kernels { + void apply_gravity_displacement_force_residual( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &densityTrue, + const mfem::Vector &gravityGradientTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &residualTrue + ); + + void apply_gravity_displacement_force_density_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_gravity_displacement_force_gradient_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &gravityGradientVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_gravity_displacement_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_gravity_displacement_force_complete_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &baseGravityGradientTrue, + const mfem::Vector &gravityGradientVariationTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm b/libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm index 7288fa2..f3cf927 100644 --- a/libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm +++ b/libmeanfield/interface/operators/kernels/pressure_force_kernels.cppm @@ -6,15 +6,35 @@ export module mean_field:operators.kernels.pressure_force; export import :fem; export import :mapping.domain_mapper; -export import :physics.barotrope; +export import :eos.polytrope; export namespace mean_field::operators::kernels { void apply_pressure_force_residual( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope, + const eos::Polytrope &barotrope, const mfem::Vector &enthalpyTrue, const mfem::Vector &displacementTrue, mfem::Vector &residualTrue ); + + void apply_pressure_force_enthalpy_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const mfem::Vector &baseEnthalpyTrue, + const mfem::Vector &enthalpyVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_pressure_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const mfem::Vector &baseEnthalpyTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); } // namespace mean_field::operators::kernels \ No newline at end of file diff --git a/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm b/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm new file mode 100644 index 0000000..cedf8cc --- /dev/null +++ b/libmeanfield/interface/operators/kernels/rotation_displacement_force_kernels.cppm @@ -0,0 +1,64 @@ +module; + +#include + +export module mean_field:operators.kernels.rotational_displacement_force; + +export import :fem; +export import :mapping.domain_mapper; +export import :physics.rigid_rotation; + +export namespace mean_field::operators::kernels { + /* + * Rotational contribution to the displacement row: + * + * R_d^rotation(w) + * = -int_{Omega_star} rho grad(Psi_rotation) . w dV + * = int_{Omega_star} + * rho [Omega x (Omega x (x - x_0))] . w dV. + * + * RigidRotation stores the positive potential + * + * Psi_rotation = 0.5 |Omega x (x - x_0)|^2. + * + * Vacuum elements are excluded exactly. + */ + void apply_rotational_displacement_force_residual( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &densityTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &residualTrue + ); + + void apply_rotational_displacement_force_density_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_rotational_displacement_force_displacement_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); + + void apply_rotational_displacement_force_complete_action( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const physics::RigidRotation &rotation, + const mfem::Vector &baseDensityTrue, + const mfem::Vector &densityVariationTrue, + const mfem::Vector &displacementVariationTrue, + const mfem::Vector &displacementTrue, + mfem::Vector &actionTrue + ); +} // namespace mean_field::operators::kernels diff --git a/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm b/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm index f53abcf..e3b5455 100644 --- a/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm +++ b/libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm @@ -6,29 +6,44 @@ module; export module mean_field:operators.prepared_barotropic_closure; +export import :eos.polytrope; export import :fem; +export import :field.mfem; export import :mapping.domain_mapper; -export import :physics.barotrope; +export import :operators.context.barotropic_closure_linearization; export namespace mean_field::operators { + struct PreparedBarotropicClosureReport final { + context::barotropic::BarotropicClosurePreparationReport contextReport; + bool preparedElementData{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return contextReport.DidAnyWork() || preparedElementData; + } + }; + class PreparedBarotropicClosureOperator final : public mfem::Operator { public: PreparedBarotropicClosureOperator( const fem::FEM &f, const mapping::DomainMapperStateless &domainMapper, - const physics::PolytropicBarotrope &barotrope + const eos::Polytrope &equationOfState ); - void Prepare( - const mfem::Vector &baseDensityTrue, - const mfem::Vector &baseEnthalpyTrue, - const mfem::Vector &displacementTrue + PreparedBarotropicClosureOperator(const PreparedBarotropicClosureOperator &) = delete; + PreparedBarotropicClosureOperator &operator=(const PreparedBarotropicClosureOperator &) = delete; + PreparedBarotropicClosureOperator(PreparedBarotropicClosureOperator &&) = delete; + PreparedBarotropicClosureOperator &operator=(PreparedBarotropicClosureOperator &&) = delete; + + PreparedBarotropicClosureReport Prepare( + const context::barotropic::BarotropicClosureStateView &state, + const context::barotropic::BarotropicClosureDependencies &dependencies ); void Mult( - const mfem::Vector &densityVariationTrue, - const mfem::Vector &enthalpyVariationTrue, - const mfem::Vector &displacementVariationTrue, + const mfem::Vector &densityVariation, + const mfem::Vector &enthalpyVariation, + const mfem::Vector &displacementVariation, mfem::Vector &action ) const; @@ -40,20 +55,33 @@ export namespace mean_field::operators { void BuildResidual(mfem::Vector &residual) const; [[nodiscard]] bool IsPrepared() const noexcept; - [[nodiscard]] std::uint64_t GetPreparationCount() const noexcept; - [[nodiscard]] int GetDensitySize() const noexcept; - [[nodiscard]] int GetEnthalpySize() const noexcept; + [[nodiscard]] int GetDisplacementSize() const noexcept; + + [[nodiscard]] const context::barotropic::BarotropicClosureLinearizationContext &GetContext() const noexcept; + [[nodiscard]] const context::barotropic::BarotropicClosurePreparationStatistics & + GetContextPreparationStatistics() const noexcept; private: + struct ConstructionData; + + [[nodiscard]] static ConstructionData MakeConstructionData(const fem::FEM &f); + + PreparedBarotropicClosureOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + ConstructionData constructionData + ); + void VerifyPrepared() const; - void Mult( + void ApplyThermodynamicActionFull( const mfem::Vector &densityVariationTrue, const mfem::Vector &enthalpyVariationTrue, - mfem::Vector &action + mfem::Vector &actionTrue ) const; struct ElementPAData { @@ -73,7 +101,13 @@ export namespace mean_field::operators { const fem::FEM &m_fem; const mapping::DomainMapperStateless &m_domainMapper; - const physics::PolytropicBarotrope &m_barotrope; + const eos::Polytrope &m_equationOfState; + + field::FieldDofMap m_densityMap; + field::FieldDofMap m_enthalpyMap; + field::FieldDofMap m_displacementMap; + + context::barotropic::BarotropicClosureLinearizationContext m_context; std::vector m_elements; @@ -81,8 +115,12 @@ export namespace mean_field::operators { mfem::Vector m_baseEnthalpyTrue; mfem::Vector m_baseDisplacementTrue; - int m_densitySize{0}; - int m_enthalpySize{0}; + mutable mfem::Vector m_densityVariationTrue; + mutable mfem::Vector m_enthalpyVariationTrue; + mutable mfem::Vector m_displacementVariationTrue; + mutable mfem::Vector m_fullThermodynamicAction; + mutable mfem::Vector m_fullDisplacementAction; + mutable mfem::Vector m_fullResidual; std::uint64_t m_preparationCount{0}; bool m_isPrepared{false}; diff --git a/libmeanfield/interface/operators/prepared_displacement_operator.cppm b/libmeanfield/interface/operators/prepared_displacement_operator.cppm new file mode 100644 index 0000000..c190806 --- /dev/null +++ b/libmeanfield/interface/operators/prepared_displacement_operator.cppm @@ -0,0 +1,198 @@ +module; + +#include +#include + +#include + +export module mean_field:operators.prepared_displacement_residual; + +export import :fem; +export import :mapping.domain_mapper; +export import :operators.context.gravity_field; +export import :operators.prepared_gravity_displacement_force; +export import :operators.prepared_pressure_force; +export import :operators.prepared_rotational_displacement_force; +export import :eos.polytrope; +export import :physics.rigid_rotation; +export import :utils.blocks; + +export namespace mean_field::operators { + struct DisplacementResidualDependencyStamp final { + std::uint64_t identity{0}; + std::uint64_t revision{0}; + + constexpr auto operator<=>(const DisplacementResidualDependencyStamp &) const = default; + }; + + struct DisplacementResidualDependencies final { + DisplacementResidualDependencyStamp discretization; + DisplacementResidualDependencyStamp density; + DisplacementResidualDependencyStamp displacement; + DisplacementResidualDependencyStamp gravityGradient; + DisplacementResidualDependencyStamp enthalpy; + DisplacementResidualDependencyStamp rotation; + + constexpr auto operator<=>(const DisplacementResidualDependencies &) const = default; + }; + + /* + * Density, displacement, and gravity gradient deliberately do not appear + * here. They are obtained from the shared, already-prepared gravity-field + * linearization context so every mechanical-force contribution consumes + * the same frozen density and geometry as the gravity equations. + */ + struct DisplacementResidualStateView final { + const mfem::Vector &enthalpy; + }; + + struct PreparedDisplacementResidualReport final { + PreparedPressureForceReport pressure; + PreparedGravityDisplacementForceReport gravity; + PreparedRotationalDisplacementForceReport rotation; + + bool assembledResidual{false}; + + [[nodiscard]] bool DidAnyChildWork() const noexcept { + return pressure.DidAnyWork() || gravity.DidAnyWork() || rotation.DidAnyWork(); + } + + [[nodiscard]] bool DidAnyWork() const noexcept { + return DidAnyChildWork() || assembledResidual; + } + }; + + struct PreparedDisplacementResidualActionStatistics final { + std::uint64_t densityApplications{0}; + std::uint64_t displacementApplications{0}; + std::uint64_t gravityGradientApplications{0}; + std::uint64_t enthalpyApplications{0}; + std::uint64_t completeApplications{0}; + + constexpr auto operator<=>(const PreparedDisplacementResidualActionStatistics &) const = default; + }; + + /* + * Row-level composer for + * + * R_d = R_d^pressure + R_d^gravity + R_d^rotation. + * + * This class owns the three prepared contributors and only orchestrates + * their existing residual and Jacobian APIs. It contains no force kernel + * and no independent copy of the gravity linearization context. + */ + class PreparedDisplacementResidualOperator final { + public: + PreparedDisplacementResidualOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &barotrope, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ); + + PreparedDisplacementResidualOperator(const PreparedDisplacementResidualOperator &) = delete; + + PreparedDisplacementResidualOperator &operator=(const PreparedDisplacementResidualOperator &) = delete; + + PreparedDisplacementResidualOperator(PreparedDisplacementResidualOperator &&) = delete; + + PreparedDisplacementResidualOperator &operator=(PreparedDisplacementResidualOperator &&) = delete; + + PreparedDisplacementResidualReport Prepare( + const DisplacementResidualStateView &state, + const DisplacementResidualDependencies &dependencies, + const physics::RigidRotation &rotation + ); + + void BuildResidual(mfem::Vector &residual) const; + + void ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const; + + void ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + void ApplyGravityGradientJacobianAction( + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const; + + void ApplyEnthalpyJacobianAction( + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const; + + void ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + const mfem::Vector &gravityGradientVariation, + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const; + + [[nodiscard]] bool IsPrepared() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualPreparationCount() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept; + + [[nodiscard]] const PreparedDisplacementResidualActionStatistics &GetActionStatistics() const noexcept; + + [[nodiscard]] const PreparedPressureForceOperator &GetPressureOperator() const noexcept; + + [[nodiscard]] const PreparedGravityDisplacementForceOperator &GetGravityOperator() const noexcept; + + [[nodiscard]] const PreparedRotationalDisplacementForceOperator &GetRotationalOperator() const noexcept; + + [[nodiscard]] const fem::FEM &GetFEM() const noexcept; + + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetGravityContext() const noexcept; + + private: + void AssembleResidual(); + void VerifyPrepared() const; + + const fem::FEM &m_fem; + const mapping::DomainMapperStateless &m_domainMapper; + const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; + + PreparedPressureForceOperator m_pressureOperator; + PreparedGravityDisplacementForceOperator m_gravityOperator; + PreparedRotationalDisplacementForceOperator m_rotationalOperator; + + DisplacementResidualDependencies m_preparedDependencies; + mfem::Vector m_cachedResidual; + + std::uint64_t m_residualPreparationCount{0}; + mutable std::uint64_t m_residualApplicationCount{0}; + mutable PreparedDisplacementResidualActionStatistics m_actionStatistics; + + bool m_isPrepared{false}; + }; + + using DisplacementResidualLayout = utils::blocks::form_layout; + + class PreparedDisplacementResidualJacobianOperator final : public mfem::Operator { + public: + PreparedDisplacementResidualJacobianOperator( + const DisplacementResidualLayout &layout, + const PreparedDisplacementResidualOperator &preparedOperator + ); + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] const DisplacementResidualLayout &GetLayout() const noexcept; + + private: + DisplacementResidualLayout m_layout; + const PreparedDisplacementResidualOperator &m_preparedOperator; + }; +} // namespace mean_field::operators diff --git a/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm b/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm new file mode 100644 index 0000000..8bc7e6f --- /dev/null +++ b/libmeanfield/interface/operators/prepared_gravity_displacement_force.cppm @@ -0,0 +1,150 @@ +module; + +#include +#include + +#include + +export module mean_field:operators.prepared_gravity_displacement_force; + +export import :fem; +export import :mapping.domain_mapper; +export import :operators.context.gravity_field; +export import :utils.blocks; + +export namespace mean_field::operators { + struct PreparedGravityDisplacementForceReport final { + bool preparedResidual{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return preparedResidual; + } + }; + + struct PreparedGravityDisplacementForceColumnStatistics final { + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedGravityDisplacementForceColumnStatistics &) const = default; + }; + + struct PreparedGravityDisplacementForceCompleteStatistics final { + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedGravityDisplacementForceCompleteStatistics &) const = default; + }; + + class PreparedGravityDisplacementForceOperator final { + public: + PreparedGravityDisplacementForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ); + + PreparedGravityDisplacementForceOperator(const PreparedGravityDisplacementForceOperator &) = delete; + + PreparedGravityDisplacementForceOperator &operator=(const PreparedGravityDisplacementForceOperator &) = delete; + + PreparedGravityDisplacementForceOperator(PreparedGravityDisplacementForceOperator &&) = delete; + + PreparedGravityDisplacementForceOperator &operator=(PreparedGravityDisplacementForceOperator &&) = delete; + + /* + * Freeze the already-prepared shared gravity context. The caller must + * first prepare GravityFieldLinearizationContext for the desired + * state. Repeated calls with unchanged revisions do no work. + */ + PreparedGravityDisplacementForceReport Prepare(); + + void BuildResidual(mfem::Vector &residual) const; + + void ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const; + + void ApplyGravityGradientJacobianAction( + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const; + + void ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + void ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + const mfem::Vector &gravityGradientVariation, + mfem::Vector &action + ) const; + + [[nodiscard]] bool IsPrepared() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualPreparationCount() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept; + + [[nodiscard]] const PreparedGravityDisplacementForceColumnStatistics & + GetDensityJacobianStatistics() const noexcept; + + [[nodiscard]] const PreparedGravityDisplacementForceColumnStatistics & + GetGravityGradientJacobianStatistics() const noexcept; + + [[nodiscard]] const PreparedGravityDisplacementForceColumnStatistics & + GetDisplacementJacobianStatistics() const noexcept; + + [[nodiscard]] const PreparedGravityDisplacementForceCompleteStatistics & + GetCompleteJacobianStatistics() const noexcept; + + [[nodiscard]] const fem::FEM &GetFEM() const noexcept; + + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetGravityContext() const noexcept; + + private: + void VerifyPrepared() const; + + const fem::FEM &m_fem; + const mapping::DomainMapperStateless &m_domainMapper; + const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; + + context::gravity_field::GravityFieldRevisions m_preparedRevisions; + mfem::Vector m_cachedResidual; + + std::uint64_t m_residualPreparationCount{0}; + mutable std::uint64_t m_residualApplicationCount{0}; + + mutable PreparedGravityDisplacementForceColumnStatistics m_densityJacobianStatistics; + + mutable PreparedGravityDisplacementForceColumnStatistics m_gravityGradientJacobianStatistics; + + mutable PreparedGravityDisplacementForceColumnStatistics m_displacementJacobianStatistics; + + mutable PreparedGravityDisplacementForceCompleteStatistics m_completeJacobianStatistics; + + bool m_isPrepared{false}; + }; + + using GravityDisplacementForceLayout = utils::blocks::form_layout; + + class PreparedGravityDisplacementForceJacobianOperator final : public mfem::Operator { + public: + PreparedGravityDisplacementForceJacobianOperator( + const GravityDisplacementForceLayout &layout, + const PreparedGravityDisplacementForceOperator &preparedOperator + ); + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] const GravityDisplacementForceLayout &GetLayout() const noexcept; + + private: + GravityDisplacementForceLayout m_layout; + const PreparedGravityDisplacementForceOperator &m_preparedOperator; + }; +} // namespace mean_field::operators diff --git a/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm b/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm index 8d27969..2d86fc7 100644 --- a/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm +++ b/libmeanfield/interface/operators/prepared_hydrostatic_equilibrium_operator.cppm @@ -25,8 +25,7 @@ export namespace mean_field::operators { bool preparedResidual{false}; [[nodiscard]] bool DidAnyWork() const noexcept { - return contextReport.DidAnyWork() || updatedRotation || - preparedAlgebraicJacobianBlocks || + return contextReport.DidAnyWork() || updatedRotation || preparedAlgebraicJacobianBlocks || preparedDisplacementJacobianData || preparedResidual; } }; @@ -38,26 +37,20 @@ export namespace mean_field::operators { std::uint64_t bernoulliConstantApplications{0}; std::uint64_t combinedApplications{0}; - constexpr auto operator<=>( - const PreparedHydrostaticAlgebraicJacobianStatistics & - ) const = default; + constexpr auto operator<=>(const PreparedHydrostaticAlgebraicJacobianStatistics &) const = default; }; struct PreparedHydrostaticDisplacementJacobianStatistics { std::uint64_t preparations{0}; std::uint64_t applications{0}; - constexpr auto operator<=>( - const PreparedHydrostaticDisplacementJacobianStatistics & - ) const = default; + constexpr auto operator<=>(const PreparedHydrostaticDisplacementJacobianStatistics &) const = default; }; struct PreparedHydrostaticCompleteJacobianStatistics { std::uint64_t applications{0}; - constexpr auto operator<=>( - const PreparedHydrostaticCompleteJacobianStatistics & - ) const = default; + constexpr auto operator<=>(const PreparedHydrostaticCompleteJacobianStatistics &) const = default; }; enum class HydrostaticJacobianInputBlock : int { @@ -94,24 +87,17 @@ export namespace mean_field::operators { const mapping::DomainMapperStateless &domainMapper ); - PreparedHydrostaticEquilibriumOperator( - const PreparedHydrostaticEquilibriumOperator & - ) = delete; + PreparedHydrostaticEquilibriumOperator(const PreparedHydrostaticEquilibriumOperator &) = delete; - PreparedHydrostaticEquilibriumOperator & - operator=(const PreparedHydrostaticEquilibriumOperator &) = delete; + PreparedHydrostaticEquilibriumOperator &operator=(const PreparedHydrostaticEquilibriumOperator &) = delete; - PreparedHydrostaticEquilibriumOperator( - PreparedHydrostaticEquilibriumOperator && - ) = delete; + PreparedHydrostaticEquilibriumOperator(PreparedHydrostaticEquilibriumOperator &&) = delete; - PreparedHydrostaticEquilibriumOperator & - operator=(PreparedHydrostaticEquilibriumOperator &&) = delete; + PreparedHydrostaticEquilibriumOperator &operator=(PreparedHydrostaticEquilibriumOperator &&) = delete; PreparedHydrostaticEquilibriumReport Prepare( const context::hydrostatic::HydrostaticEquilibriumStateView &state, - const context::hydrostatic::HydrostaticEquilibriumDependencies - &dependencies, + const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies, const physics::RigidRotation &rotation ); @@ -154,15 +140,12 @@ export namespace mean_field::operators { [[nodiscard]] bool IsPrepared() const noexcept; - [[nodiscard]] const context::hydrostatic:: - HydrostaticPreparationStatistics & - GetContextPreparationStatistics() const noexcept; + [[nodiscard]] const context::hydrostatic::HydrostaticPreparationStatistics & + GetContextPreparationStatistics() const noexcept; - [[nodiscard]] std::uint64_t - GetResidualPreparationCount() const noexcept; + [[nodiscard]] std::uint64_t GetResidualPreparationCount() const noexcept; - [[nodiscard]] std::uint64_t - GetResidualApplicationCount() const noexcept; + [[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept; [[nodiscard]] const PreparedHydrostaticAlgebraicJacobianStatistics & GetAlgebraicJacobianStatistics() const noexcept; @@ -203,11 +186,9 @@ export namespace mean_field::operators { mfem::Vector quadratureWeights; std::vector baseMappingContexts; - std::optional - baseDisplacementData; + std::optional baseDisplacementData; - std::optional - compactificationData; + std::optional compactificationData; mfem::Vector rotationPotential; mfem::DenseMatrix rotationGradient; @@ -242,20 +223,16 @@ export namespace mean_field::operators { mutable std::uint64_t m_residualApplicationCount{0}; - mutable PreparedHydrostaticAlgebraicJacobianStatistics - m_algebraicJacobianStatistics; + mutable PreparedHydrostaticAlgebraicJacobianStatistics m_algebraicJacobianStatistics; - mutable PreparedHydrostaticDisplacementJacobianStatistics - m_displacementJacobianStatistics; + mutable PreparedHydrostaticDisplacementJacobianStatistics m_displacementJacobianStatistics; - mutable PreparedHydrostaticCompleteJacobianStatistics - m_completeJacobianStatistics; + mutable PreparedHydrostaticCompleteJacobianStatistics m_completeJacobianStatistics; bool m_isPrepared{false}; }; - class PreparedHydrostaticEquilibriumJacobianOperator final - : public mfem::Operator { + class PreparedHydrostaticEquilibriumJacobianOperator final : public mfem::Operator { public: PreparedHydrostaticEquilibriumJacobianOperator( const fem::FEM &f, @@ -267,8 +244,7 @@ export namespace mean_field::operators { mfem::Vector &action ) const override; - [[nodiscard]] const HydrostaticJacobianBlockLayout & - GetLayout() const noexcept; + [[nodiscard]] const HydrostaticJacobianBlockLayout &GetLayout() const noexcept; private: HydrostaticJacobianBlockLayout m_layout; diff --git a/libmeanfield/interface/operators/prepared_mass_normalization.cppm b/libmeanfield/interface/operators/prepared_mass_normalization.cppm new file mode 100644 index 0000000..e32e539 --- /dev/null +++ b/libmeanfield/interface/operators/prepared_mass_normalization.cppm @@ -0,0 +1,185 @@ +module; + +#include +#include +#include +#include + +export module mean_field:operators.prepared_mass_normalization; + +export import :fem; +export import :mapping.domain_mapper; +export import :operators.context.gravity_field; +export import :utils.blocks; + +export namespace mean_field::operators { + struct MassNormalizationDependencyStamp final { + std::uint64_t identity{0}; + std::uint64_t revision{0}; + + constexpr auto operator<=>(const MassNormalizationDependencyStamp &) const = default; + }; + + struct MassNormalizationDependencies final { + MassNormalizationDependencyStamp discretization; + MassNormalizationDependencyStamp density; + MassNormalizationDependencyStamp displacement; + MassNormalizationDependencyStamp targetMass; + + constexpr auto operator<=>(const MassNormalizationDependencies &) const = default; + }; + + struct MassNormalizationStateView final { + double targetMass{0.0}; + }; + + struct PreparedMassNormalizationReport final { + bool rebuiltStaticPlan{false}; + bool refreshedGeometry{false}; + bool refreshedDensity{false}; + bool updatedTargetMass{false}; + bool assembledResidual{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return rebuiltStaticPlan || refreshedGeometry || refreshedDensity || updatedTargetMass || assembledResidual; + } + }; + + struct PreparedMassNormalizationActionStatistics final { + std::uint64_t densityApplications{0}; + std::uint64_t displacementApplications{0}; + std::uint64_t completeApplications{0}; + + constexpr auto operator<=>(const PreparedMassNormalizationActionStatistics &) const = default; + }; + + /* + * Prepared scalar row + * + * R_M(rho, d) = integral_{Omega_star(d)} rho dV - M_target. + * + * Density and displacement are borrowed from the shared gravity-field + * linearization context. This keeps the mass row on exactly the same + * frozen state and geometry as the gravity and mechanical rows. + */ + class PreparedMassNormalizationOperator final { + public: + PreparedMassNormalizationOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const context::gravity_field::GravityFieldLinearizationContext &gravityContext + ); + + PreparedMassNormalizationOperator(const PreparedMassNormalizationOperator &) = delete; + PreparedMassNormalizationOperator &operator=(const PreparedMassNormalizationOperator &) = delete; + PreparedMassNormalizationOperator(PreparedMassNormalizationOperator &&) = delete; + PreparedMassNormalizationOperator &operator=(PreparedMassNormalizationOperator &&) = delete; + + PreparedMassNormalizationReport Prepare( + const MassNormalizationStateView &state, + const MassNormalizationDependencies &dependencies + ); + + void BuildResidual(mfem::Vector &residual) const; + + void ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const; + + void ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + void ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + [[nodiscard]] bool IsPrepared() const noexcept; + [[nodiscard]] double GetCurrentMass() const; + [[nodiscard]] double GetTargetMass() const; + [[nodiscard]] std::uint64_t GetPreparationCount() const noexcept; + [[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept; + [[nodiscard]] const PreparedMassNormalizationActionStatistics &GetActionStatistics() const noexcept; + [[nodiscard]] const fem::FEM &GetFEM() const noexcept; + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetGravityContext() const noexcept; + + private: + struct QuadraturePointData final { + mfem::IntegrationPoint integrationPoint; + mfem::Vector densityShape; + mapping::VolumeMappingContext mappingContext; + double density{0.0}; + }; + + struct ElementPAData final { + int elementId{-1}; + + mfem::Array densityDofs; + mfem::Array displacementDofs; + mfem::Array compactificationDofs; + + mfem::DofTransformation *densityDofTransformation{nullptr}; + mfem::DofTransformation *displacementDofTransformation{nullptr}; + mfem::DofTransformation *compactificationDofTransformation{nullptr}; + + mfem::Vector baseDisplacement; + mfem::Vector compactification; + + std::vector quadraturePoints; + }; + + void BuildStaticPlan(); + void RefreshGeometry(const mfem::Vector &displacement); + void RefreshDensity(const mfem::Vector &density); + void AssembleResidual(); + void VerifyPrepared() const; + + [[nodiscard]] double EvaluateDensityActionLocal(const mfem::Vector &densityVariation) const; + + [[nodiscard]] double EvaluateDisplacementActionLocal(const mfem::Vector &displacementVariation) const; + + [[nodiscard]] double GlobalSum(double localValue) const; + + const fem::FEM &m_fem; + const mapping::DomainMapperStateless &m_domainMapper; + const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext; + + std::vector m_elements; + MassNormalizationDependencies m_preparedDependencies; + mfem::Vector m_cachedResidual; + + double m_currentMass{0.0}; + double m_targetMass{0.0}; + + std::uint64_t m_preparationCount{0}; + mutable std::uint64_t m_residualApplicationCount{0}; + mutable PreparedMassNormalizationActionStatistics m_actionStatistics; + bool m_isPrepared{false}; + }; + + using MassNormalizationLayout = utils::blocks::form_layout; + + class PreparedMassNormalizationJacobianOperator final : public mfem::Operator { + public: + PreparedMassNormalizationJacobianOperator( + const MassNormalizationLayout &layout, + const PreparedMassNormalizationOperator &preparedOperator + ); + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] const MassNormalizationLayout &GetLayout() const noexcept; + + private: + MassNormalizationLayout m_layout; + const PreparedMassNormalizationOperator &m_preparedOperator; + }; +} // namespace mean_field::operators diff --git a/libmeanfield/interface/operators/prepared_pressure_force.cppm b/libmeanfield/interface/operators/prepared_pressure_force.cppm new file mode 100644 index 0000000..6dd2589 --- /dev/null +++ b/libmeanfield/interface/operators/prepared_pressure_force.cppm @@ -0,0 +1,304 @@ +module; + +#include +#include +#include +#include +#include + +#include + +export module mean_field:operators.prepared_pressure_force; + +export import :eos.polytrope; +export import :fem; +export import :field.mfem; +export import :mapping.domain_mapper; +export import :operators.context.pressure_force; +export import :utils.blocks; + +export namespace mean_field::operators { + struct PreparedPressureForceReport final { + context::pressure_force::PressureForcePreparationReport contextReport; + + bool preparedEnthalpyJacobianData{false}; + bool preparedDisplacementJacobianData{false}; + bool preparedResidual{false}; + + [[nodiscard]] + bool DidAnyWork() const noexcept { + return contextReport.DidAnyWork() || preparedEnthalpyJacobianData || preparedDisplacementJacobianData || + preparedResidual; + } + }; + + struct PreparedPressureForceEnthalpyJacobianStatistics final { + std::uint64_t preparations{0}; + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedPressureForceEnthalpyJacobianStatistics &) const = default; + }; + + struct PreparedPressureForceDisplacementJacobianStatistics final { + std::uint64_t preparations{0}; + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedPressureForceDisplacementJacobianStatistics &) const = default; + }; + + struct PreparedPressureForceCompleteJacobianStatistics final { + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedPressureForceCompleteJacobianStatistics &) const = default; + }; + + /* + * Prepared pressure contribution + * + * R_d^P(w) + * = + * - integral_{Omega_star(d)} + * P(h) div(w) dV. + * + * Public state and Jacobian directions are expressed in FieldDof + * coordinates. + * + * Current registry: + * + * h -> Stellar -> reduced + * d -> All -> identity/full + * R_d -> All -> identity/full + * + * Full MFEM true/local vectors are private implementation details. + */ + class PreparedPressureForceOperator final { + public: + PreparedPressureForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState + ); + + PreparedPressureForceOperator(const PreparedPressureForceOperator &) = delete; + + PreparedPressureForceOperator &operator=(const PreparedPressureForceOperator &) = delete; + + PreparedPressureForceOperator(PreparedPressureForceOperator &&) = delete; + + PreparedPressureForceOperator &operator=(PreparedPressureForceOperator &&) = delete; + + PreparedPressureForceReport Prepare( + const context::pressure_force::PressureForceStateView &state, + const context::pressure_force::PressureForceDependencies &dependencies + ); + + void BuildResidual(mfem::Vector &residual) const; + + void ApplyEnthalpyJacobianAction( + const mfem::Vector &enthalpyVariation, + mfem::Vector &action + ) const; + + void ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + void ApplyCompleteJacobianAction( + const mfem::Vector &enthalpyVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + [[nodiscard]] + bool IsPrepared() const noexcept; + + [[nodiscard]] + int GetEnthalpySize() const noexcept; + + [[nodiscard]] + int GetDisplacementSize() const noexcept; + + [[nodiscard]] + const context::pressure_force::PressureForceLinearizationContext &GetContext() const noexcept; + + [[nodiscard]] + const context::pressure_force::PressureForcePreparationStatistics & + GetContextPreparationStatistics() const noexcept; + + [[nodiscard]] + std::uint64_t GetResidualPreparationCount() const noexcept; + + [[nodiscard]] + std::uint64_t GetResidualApplicationCount() const noexcept; + + [[nodiscard]] + const PreparedPressureForceEnthalpyJacobianStatistics &GetEnthalpyJacobianStatistics() const noexcept; + + [[nodiscard]] + const PreparedPressureForceDisplacementJacobianStatistics &GetDisplacementJacobianStatistics() const noexcept; + + [[nodiscard]] + const PreparedPressureForceCompleteJacobianStatistics &GetCompleteJacobianStatistics() const noexcept; + + [[nodiscard]] + std::size_t GetStellarElementCount() const noexcept; + + [[nodiscard]] + const fem::FEM &GetFEM() const noexcept; + + private: + struct ConstructionData; + + [[nodiscard]] + static ConstructionData MakeConstructionData(const fem::FEM &f); + + PreparedPressureForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + ConstructionData constructionData + ); + + struct ElementPAData final { + int elementId{-1}; + + mfem::Array enthalpyDofs; + mfem::Array displacementDofs; + mfem::Array compactificationDofs; + + mfem::DofTransformation *enthalpyDofTransformation{nullptr}; + + mfem::DofTransformation *displacementDofTransformation{nullptr}; + + mfem::DofTransformation *compactificationDofTransformation{nullptr}; + + const mfem::IntegrationRule *integrationRule{nullptr}; + + /* + * Rows are quadrature points and columns are enthalpy DOFs. + */ + mfem::DenseMatrix enthalpyBasis; + + /* + * Each entry is: + * + * scalar displacement DOF + * x + * physical dimension. + */ + std::vector referenceTestGradients; + + std::vector physicalTestGradients; + + std::vector baseMappingContexts; + + std::optional baseDisplacementData; + + std::optional compactificationData; + + mfem::Vector quadratureWeights; + mfem::Vector pressure; + mfem::Vector pressureDerivative; + + mfem::Vector elementResidual; + mfem::DenseMatrix enthalpyJacobian; + }; + + void PrepareStaticPlan(); + void PrepareGeometry(); + void PrepareMaterialState(); + + void FinalizeDisplacementJacobianPreparation(); + + void AssembleCachedResidual(); + + void VerifyPrepared() const; + + const fem::FEM &m_fem; + + const mapping::DomainMapperStateless &m_domainMapper; + + const eos::Polytrope &m_equationOfState; + + field::FieldDofMap m_enthalpyMap; + + field::FieldDofMap m_displacementMap; + + context::pressure_force::PressureForceLinearizationContext m_context; + + std::vector m_elements; + + /* + * Canonical full-MFEM expansion of the frozen FieldDof state. + */ + mfem::Vector m_baseEnthalpyTrue; + mfem::Vector m_baseDisplacementTrue; + + /* + * Reusable Krylov work storage. + */ + mutable mfem::Vector m_enthalpyVariationTrue; + + mutable mfem::Vector m_displacementVariationTrue; + + mutable mfem::Vector m_fullDisplacementAction; + + /* + * Solver-facing cached residual in Displacement FieldDof + * coordinates. + */ + mfem::Vector m_cachedResidual; + + std::uint64_t m_residualPreparationCount{0}; + + mutable std::uint64_t m_residualApplicationCount{0}; + + mutable PreparedPressureForceEnthalpyJacobianStatistics m_enthalpyJacobianStatistics; + + mutable PreparedPressureForceDisplacementJacobianStatistics m_displacementJacobianStatistics; + + mutable PreparedPressureForceCompleteJacobianStatistics m_completeJacobianStatistics; + + bool m_isPrepared{false}; + }; + + using BarotropicEquilibriumLayout = utils::blocks::form_layout; + + /* + * Coupled-layout adapter around the modern prepared pressure-force + * Jacobian. + * + * Reads: + * + * delta d + * delta h + * + * Writes: + * + * R_d + * + * It deliberately imposes no raw-FES-size assumptions on unrelated + * coupled blocks. + */ + class PreparedPressureForceJacobianOperator final : public mfem::Operator { + public: + PreparedPressureForceJacobianOperator( + const BarotropicEquilibriumLayout &layout, + const PreparedPressureForceOperator &preparedOperator + ); + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] + const BarotropicEquilibriumLayout &GetLayout() const noexcept; + + private: + BarotropicEquilibriumLayout m_layout; + + const PreparedPressureForceOperator &m_preparedOperator; + }; +} // namespace mean_field::operators \ No newline at end of file diff --git a/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm b/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm new file mode 100644 index 0000000..ae37603 --- /dev/null +++ b/libmeanfield/interface/operators/prepared_rotation_displacement_force.cppm @@ -0,0 +1,149 @@ +module; + +#include +#include +#include + +#include + +export module mean_field:operators.prepared_rotational_displacement_force; + +export import :fem; +export import :mapping.domain_mapper; +export import :operators.context.rotational_displacement_force; +export import :physics.rigid_rotation; +export import :utils.blocks; + +export namespace mean_field::operators { + struct PreparedRotationalDisplacementForceReport final { + context::rotational_displacement_force::RotationalDisplacementForcePreparationReport contextReport; + + bool updatedRotation{false}; + bool preparedResidual{false}; + + [[nodiscard]] bool DidAnyWork() const noexcept { + return contextReport.DidAnyWork() || updatedRotation || preparedResidual; + } + }; + + struct PreparedRotationalDisplacementForceColumnStatistics final { + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedRotationalDisplacementForceColumnStatistics &) const = default; + }; + + struct PreparedRotationalDisplacementForceCompleteStatistics final { + std::uint64_t applications{0}; + + constexpr auto operator<=>(const PreparedRotationalDisplacementForceCompleteStatistics &) const = default; + }; + + class PreparedRotationalDisplacementForceOperator final { + public: + PreparedRotationalDisplacementForceOperator( + const fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper + ); + + PreparedRotationalDisplacementForceOperator(const PreparedRotationalDisplacementForceOperator &) = delete; + + PreparedRotationalDisplacementForceOperator & + operator=(const PreparedRotationalDisplacementForceOperator &) = delete; + + PreparedRotationalDisplacementForceOperator(PreparedRotationalDisplacementForceOperator &&) = delete; + + PreparedRotationalDisplacementForceOperator &operator=(PreparedRotationalDisplacementForceOperator &&) = delete; + + PreparedRotationalDisplacementForceReport Prepare( + const context::rotational_displacement_force::RotationalDisplacementForceStateView &state, + const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies, + const physics::RigidRotation &rotation + ); + + void BuildResidual(mfem::Vector &residual) const; + + void ApplyDensityJacobianAction( + const mfem::Vector &densityVariation, + mfem::Vector &action + ) const; + + void ApplyDisplacementJacobianAction( + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + void ApplyCompleteJacobianAction( + const mfem::Vector &densityVariation, + const mfem::Vector &displacementVariation, + mfem::Vector &action + ) const; + + [[nodiscard]] bool IsPrepared() const noexcept; + + [[nodiscard]] const context::rotational_displacement_force::RotationalDisplacementForcePreparationStatistics & + GetContextPreparationStatistics() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualPreparationCount() const noexcept; + + [[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept; + + [[nodiscard]] const PreparedRotationalDisplacementForceColumnStatistics & + GetDensityJacobianStatistics() const noexcept; + + [[nodiscard]] const PreparedRotationalDisplacementForceColumnStatistics & + GetDisplacementJacobianStatistics() const noexcept; + + [[nodiscard]] const PreparedRotationalDisplacementForceCompleteStatistics & + GetCompleteJacobianStatistics() const noexcept; + + [[nodiscard]] const fem::FEM &GetFEM() const noexcept; + + [[nodiscard]] const context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext & + GetContext() const noexcept; + + private: + void VerifyPrepared() const; + + const fem::FEM &m_fem; + const mapping::DomainMapperStateless &m_domainMapper; + + context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext m_context; + + std::optional m_rotation; + mfem::Vector m_cachedResidual; + + context::rotational_displacement_force::RotationalDisplacementForceDependencies m_preparedDependencies; + + std::uint64_t m_residualPreparationCount{0}; + mutable std::uint64_t m_residualApplicationCount{0}; + + mutable PreparedRotationalDisplacementForceColumnStatistics m_densityJacobianStatistics; + + mutable PreparedRotationalDisplacementForceColumnStatistics m_displacementJacobianStatistics; + + mutable PreparedRotationalDisplacementForceCompleteStatistics m_completeJacobianStatistics; + + bool m_isPrepared{false}; + }; + + using RotationalDisplacementForceLayout = utils::blocks::form_layout; + + class PreparedRotationalDisplacementForceJacobianOperator final : public mfem::Operator { + public: + PreparedRotationalDisplacementForceJacobianOperator( + const RotationalDisplacementForceLayout &layout, + const PreparedRotationalDisplacementForceOperator &preparedOperator + ); + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] const RotationalDisplacementForceLayout &GetLayout() const noexcept; + + private: + RotationalDisplacementForceLayout m_layout; + const PreparedRotationalDisplacementForceOperator &m_preparedOperator; + }; +} // namespace mean_field::operators diff --git a/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm b/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm new file mode 100644 index 0000000..0c67311 --- /dev/null +++ b/libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm @@ -0,0 +1,177 @@ +module; + +#include +#include + +#include + +export module mean_field:operators.prepared_stellar_equilibrium; + +export import :eos.polytrope; +export import :fem; +export import :field.mfem; +export import :mapping.domain_mapper; +export import :model.stellar; +export import :operators.context.gravity_field; +export import :operators.gravity_field; +export import :operators.gravity_field_jacobian; +export import :operators.prepared_barotropic_closure; +export import :operators.prepared_displacement_residual; +export import :operators.prepared_hydrostatic_equilibrium; +export import :operators.prepared_mass_normalization; +export import :physics.rigid_rotation; +export import :utils.blocks; + +export namespace mean_field::operators { + struct StellarEquilibriumDependencyStamp final { + std::uint64_t identity{0}; + std::uint64_t revision{0}; + + constexpr auto operator<=>(const StellarEquilibriumDependencyStamp &) const = default; + }; + + struct StellarEquilibriumDependencies final { + StellarEquilibriumDependencyStamp discretization; + StellarEquilibriumDependencyStamp density; + StellarEquilibriumDependencyStamp displacement; + StellarEquilibriumDependencyStamp gravityGradient; + StellarEquilibriumDependencyStamp gravityPotential; + StellarEquilibriumDependencyStamp enthalpy; + StellarEquilibriumDependencyStamp bernoulliConstant; + StellarEquilibriumDependencyStamp rotation; + StellarEquilibriumDependencyStamp targetMass; + + constexpr auto operator<=>(const StellarEquilibriumDependencies &) const = default; + }; + + struct PreparedStellarEquilibriumReport final { + context::gravity_field::GravityFieldPreparationReport gravity; + PreparedBarotropicClosureReport barotropicClosure; + PreparedHydrostaticEquilibriumReport hydrostatic; + PreparedDisplacementResidualReport displacement; + PreparedMassNormalizationReport massNormalization; + bool assembledResidual{false}; + + [[nodiscard]] bool DidAnyChildWork() const noexcept { + return gravity.DidAnyWork() || barotropicClosure.DidAnyWork() || hydrostatic.DidAnyWork() || + displacement.DidAnyWork() || massNormalization.DidAnyWork(); + } + + [[nodiscard]] bool DidAnyWork() const noexcept { + return DidAnyChildWork() || assembledResidual; + } + }; + + struct PreparedStellarEquilibriumStatistics final { + std::uint64_t residualAssemblies{0}; + std::uint64_t residualApplications{0}; + std::uint64_t jacobianApplications{0}; + + constexpr auto operator<=>(const PreparedStellarEquilibriumStatistics &) const = default; + }; + + using StellarEquilibriumLayout = utils::blocks::form_layout; + + class PreparedStellarEquilibriumOperator final : public mfem::Operator { + public: + PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + double targetMass + ); + + PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + const models::StellarModel &stellarModel + ); + + PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete; + PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete; + PreparedStellarEquilibriumOperator(PreparedStellarEquilibriumOperator &&) = delete; + PreparedStellarEquilibriumOperator &operator=(PreparedStellarEquilibriumOperator &&) = delete; + + PreparedStellarEquilibriumReport Prepare( + const mfem::Vector &state, + const StellarEquilibriumDependencies &dependencies, + const physics::RigidRotation &rotation + ); + + void BuildResidual(mfem::Vector &residual) const; + + void Mult( + const mfem::Vector &direction, + mfem::Vector &action + ) const override; + + [[nodiscard]] bool IsPrepared() const noexcept; + [[nodiscard]] double GetTargetMass() const noexcept; + [[nodiscard]] const StellarEquilibriumLayout &GetLayout() const noexcept; + [[nodiscard]] const StellarEquilibriumDependencies &GetDependencies() const; + [[nodiscard]] const PreparedStellarEquilibriumStatistics &GetStatistics() const noexcept; + + [[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext & + GetGravityContext() const noexcept; + [[nodiscard]] const GravityFieldOperator &GetGravityOperator() const noexcept; + [[nodiscard]] const GravityFieldJacobianOperator &GetGravityJacobianOperator() const noexcept; + [[nodiscard]] const PreparedBarotropicClosureOperator &GetBarotropicClosureOperator() const noexcept; + [[nodiscard]] const context::barotropic::BarotropicClosureLinearizationContext & + GetBarotropicClosureContext() const noexcept; + [[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept; + [[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept; + [[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept; + + private: + struct ConstructionData; + + static ConstructionData MakeConstructionData(fem::FEM &f); + + PreparedStellarEquilibriumOperator( + fem::FEM &f, + const mapping::DomainMapperStateless &domainMapper, + const eos::Polytrope &equationOfState, + double targetMass, + ConstructionData constructionData + ); + + void AssembleResidual(); + void VerifyPrepared() const; + + StellarEquilibriumLayout m_layout; + mfem::Array m_gravityStateOffsets; + + context::gravity_field::GravityFieldLinearizationContext m_gravityContext; + GravityFieldJacobianOperator m_gravityJacobianOperator; + GravityFieldOperator m_gravityOperator; + + PreparedBarotropicClosureOperator m_barotropicClosureOperator; + PreparedHydrostaticEquilibriumOperator m_hydrostaticOperator; + PreparedDisplacementResidualOperator m_displacementOperator; + PreparedMassNormalizationOperator m_massNormalizationOperator; + + StellarEquilibriumDependencies m_preparedDependencies; + mfem::Vector m_cachedResidual; + double m_targetMass{0.0}; + + mutable PreparedStellarEquilibriumStatistics m_statistics; + bool m_isPrepared{false}; + + field::FieldDofMap m_densityMap; + field::FieldDofMap m_displacementMap; + field::FieldDofMap m_gravityFluxMap; + field::FieldDofMap m_gravityPotentialMap; + field::FieldDofMap m_enthalpyMap; + + mfem::Vector m_fullDensity; + mfem::Vector m_fullEnthalpy; + mfem::Vector m_fullGravityState; + + mutable mfem::Vector m_fullDensityVariation; + mutable mfem::Vector m_fullEnthalpyVariation; + mutable mfem::Vector m_fullGravityDirection; + + mutable mfem::Vector m_fullEnthalpyAction; + }; +} // namespace mean_field::operators diff --git a/libmeanfield/interface/physics/barotrope.cppm b/libmeanfield/interface/physics/barotrope.cppm index 9958aca..516373d 100644 --- a/libmeanfield/interface/physics/barotrope.cppm +++ b/libmeanfield/interface/physics/barotrope.cppm @@ -27,8 +27,7 @@ export namespace mean_field::physics { ); } - if (!std::isfinite(polytropic_constant) || - polytropic_constant <= 0.0) { + if (!std::isfinite(polytropic_constant) || polytropic_constant <= 0.0) { throw std::invalid_argument( std::format( "The polytropic constant must be finite and positive. " @@ -57,8 +56,7 @@ export namespace mean_field::physics { return 0.0; } - return m_polytropic_constant * - std::pow(density, 1.0 + 1.0 / m_polytropic_index); + return m_polytropic_constant * std::pow(density, 1.0 + 1.0 / m_polytropic_index); } [[nodiscard]] double enthalpy_from_density(const double density) const { @@ -67,12 +65,10 @@ export namespace mean_field::physics { return 0.0; } - return m_enthalpy_scale * - std::pow(density, 1.0 / m_polytropic_index); + return m_enthalpy_scale * std::pow(density, 1.0 / m_polytropic_index); } - [[nodiscard]] double - density_from_enthalpy(const double enthalpy) const { + [[nodiscard]] double density_from_enthalpy(const double enthalpy) const { validate_finite(enthalpy, "enthalpy"); if (enthalpy <= 0.0) { @@ -82,20 +78,17 @@ export namespace mean_field::physics { return std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index); } - [[nodiscard]] double - pressure_from_enthalpy(const double enthalpy) const { + [[nodiscard]] double pressure_from_enthalpy(const double enthalpy) const { validate_finite(enthalpy, "enthalpy"); if (enthalpy <= 0.0) { return 0.0; } - return density_from_enthalpy(enthalpy) * enthalpy / - (m_polytropic_index + 1.0); + return density_from_enthalpy(enthalpy) * enthalpy / (m_polytropic_index + 1.0); } - [[nodiscard]] double - density_derivative_from_enthalpy(const double enthalpy) const { + [[nodiscard]] double density_derivative_from_enthalpy(const double enthalpy) const { validate_finite(enthalpy, "enthalpy"); if (enthalpy < 0.0) { return 0.0; @@ -106,13 +99,10 @@ export namespace mean_field::physics { } return m_polytropic_index / m_enthalpy_scale * - std::pow( - enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0 - ); + std::pow(enthalpy / m_enthalpy_scale, m_polytropic_index - 1.0); } - [[nodiscard]] double - pressure_derivative_from_enthalpy(const double enthalpy) const { + [[nodiscard]] double pressure_derivative_from_enthalpy(const double enthalpy) const { validate_finite(enthalpy, "enthalpy"); if (enthalpy <= 0.0) { @@ -122,8 +112,7 @@ export namespace mean_field::physics { return density_from_enthalpy(enthalpy); } - [[nodiscard]] double - pressure_derivative_from_density(const double density) const { + [[nodiscard]] double pressure_derivative_from_density(const double density) const { validate_nonnegativity(density, "density"); if (density == 0.0) { return 0.0; diff --git a/libmeanfield/interface/physics/rigid_rotation.cppm b/libmeanfield/interface/physics/rigid_rotation.cppm index 9d7c25b..284f6ed 100644 --- a/libmeanfield/interface/physics/rigid_rotation.cppm +++ b/libmeanfield/interface/physics/rigid_rotation.cppm @@ -15,36 +15,28 @@ export namespace mean_field::physics { : m_angularVelocity(angularVelocity), m_center(center) { MFEM_VERIFY( - m_angularVelocity.Size() == 3, - "RigidRotation requires a three-dimensional " - "angular-velocity vector." + m_angularVelocity.Size() == 3, "RigidRotation requires a three-dimensional " + "angular-velocity vector." ); - MFEM_VERIFY( - m_center.Size() == 3, - "RigidRotation requires a three-dimensional center." - ); + MFEM_VERIFY(m_center.Size() == 3, "RigidRotation requires a three-dimensional center."); for (int component = 0; component < 3; ++component) { MFEM_VERIFY( - std::isfinite(m_angularVelocity(component)), - "RigidRotation received a non-finite " - "angular-velocity component." + std::isfinite(m_angularVelocity(component)), "RigidRotation received a non-finite " + "angular-velocity component." ); MFEM_VERIFY( - std::isfinite(m_center(component)), - "RigidRotation received a non-finite center component." + std::isfinite(m_center(component)), "RigidRotation received a non-finite center component." ); } } - [[nodiscard]] double - potential(const mfem::Vector &physicalPosition) const { + [[nodiscard]] double potential(const mfem::Vector &physicalPosition) const { MFEM_VERIFY( - physicalPosition.Size() == 3, - "RigidRotation::potential requires a " - "three-dimensional position." + physicalPosition.Size() == 3, "RigidRotation::potential requires a " + "three-dimensional position." ); const double relativeX = physicalPosition(0) - m_center(0); @@ -53,14 +45,11 @@ export namespace mean_field::physics { const double relativeZ = physicalPosition(2) - m_center(2); - const double crossX = m_angularVelocity(1) * relativeZ - - m_angularVelocity(2) * relativeY; + const double crossX = m_angularVelocity(1) * relativeZ - m_angularVelocity(2) * relativeY; - const double crossY = m_angularVelocity(2) * relativeX - - m_angularVelocity(0) * relativeZ; + const double crossY = m_angularVelocity(2) * relativeX - m_angularVelocity(0) * relativeZ; - const double crossZ = m_angularVelocity(0) * relativeY - - m_angularVelocity(1) * relativeX; + const double crossZ = m_angularVelocity(0) * relativeY - m_angularVelocity(1) * relativeX; return 0.5 * (crossX * crossX + crossY * crossY + crossZ * crossZ); } @@ -70,48 +59,101 @@ export namespace mean_field::physics { const mfem::Vector &physicalPositionVariation ) const { MFEM_VERIFY( - physicalPosition.Size() == 3, - "RigidRotation derivative requires a " - "three-dimensional position." + physicalPosition.Size() == 3, "RigidRotation derivative requires a " + "three-dimensional position." ); MFEM_VERIFY( - physicalPositionVariation.Size() == 3, - "RigidRotation derivative requires a " - "three-dimensional direction." + physicalPositionVariation.Size() == 3, "RigidRotation derivative requires a " + "three-dimensional direction." ); double angularVelocitySquared = 0.0; double angularVelocityDotPosition = 0.0; for (int component = 0; component < 3; ++component) { - const double relativePosition = - physicalPosition(component) - m_center(component); + const double relativePosition = physicalPosition(component) - m_center(component); - angularVelocitySquared += - m_angularVelocity(component) * m_angularVelocity(component); + angularVelocitySquared += m_angularVelocity(component) * m_angularVelocity(component); - angularVelocityDotPosition += - m_angularVelocity(component) * relativePosition; + angularVelocityDotPosition += m_angularVelocity(component) * relativePosition; } double derivative = 0.0; for (int component = 0; component < 3; ++component) { - const double relativePosition = - physicalPosition(component) - m_center(component); + const double relativePosition = physicalPosition(component) - m_center(component); - const double gradientComponent = - angularVelocitySquared * relativePosition - - angularVelocityDotPosition * m_angularVelocity(component); + const double gradientComponent = angularVelocitySquared * relativePosition - + angularVelocityDotPosition * m_angularVelocity(component); - derivative += - gradientComponent * physicalPositionVariation(component); + derivative += gradientComponent * physicalPositionVariation(component); } return derivative; } + /* + * Gradient of the positive rigid-rotation potential + * + * Psi = 0.5 |Omega x (x - x_0)|^2. + * + * This points away from the rotation axis. The rotational + * displacement residual uses its negative. + */ + void potential_gradient( + const mfem::Vector &physicalPosition, + mfem::Vector &gradient + ) const { + MFEM_VERIFY( + physicalPosition.Size() == 3, "RigidRotation::potential_gradient requires a " + "three-dimensional position." + ); + + double angularVelocitySquared = 0.0; + double angularVelocityDotPosition = 0.0; + + for (int component = 0; component < 3; ++component) { + const double relativePosition = physicalPosition(component) - m_center(component); + + angularVelocitySquared += m_angularVelocity(component) * m_angularVelocity(component); + + angularVelocityDotPosition += m_angularVelocity(component) * relativePosition; + } + + gradient.SetSize(3); + + for (int component = 0; component < 3; ++component) { + const double relativePosition = physicalPosition(component) - m_center(component); + + gradient(component) = angularVelocitySquared * relativePosition - + angularVelocityDotPosition * m_angularVelocity(component); + } + } + + /* + * Hessian action of Psi. The Hessian is constant for rigid + * rotation, so only the physical-position direction is required. + */ + void potential_gradient_directional_derivative( + const mfem::Vector &physicalPositionVariation, + mfem::Vector &gradientVariation + ) const { + MFEM_VERIFY( + physicalPositionVariation.Size() == 3, "RigidRotation gradient derivative requires a " + "three-dimensional direction." + ); + + const double angularVelocitySquared = m_angularVelocity * m_angularVelocity; + + const double angularVelocityDotVariation = m_angularVelocity * physicalPositionVariation; + + gradientVariation.SetSize(3); + gradientVariation = physicalPositionVariation; + gradientVariation *= angularVelocitySquared; + gradientVariation.Add(-angularVelocityDotVariation, m_angularVelocity); + } + [[nodiscard]] const mfem::Vector &angular_velocity() const noexcept { return m_angularVelocity; } diff --git a/libmeanfield/interface/quadrature/mfem.cppm b/libmeanfield/interface/quadrature/mfem.cppm index c86587d..21560a3 100644 --- a/libmeanfield/interface/quadrature/mfem.cppm +++ b/libmeanfield/interface/quadrature/mfem.cppm @@ -108,12 +108,9 @@ export namespace mean_field::quadrature { const Query &query, const mfem::Geometry::Type geometry ) const { - const Resolution resolution = policy.resolve(query); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(geometry, resolution.order); - return { - .resolution = resolution, .integration_rule = &integration_rule - }; + const Resolution resolution = policy.resolve(query); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(geometry, resolution.order); + return {.resolution = resolution, .integration_rule = &integration_rule}; } MfemRule RuleFactory::get( @@ -146,12 +143,10 @@ export namespace mean_field::quadrature { "The H(div) element order does not match the registered gravity " "flux." ); - const Query query = - GravityField::make_query( - role, transformation.OrderW(), {}, domain, mapping - ); - const auto [resolution, integration_rule] = - get(query, element.GetGeomType()); + const Query query = GravityField::make_query( + role, transformation.OrderW(), {}, domain, mapping + ); + const auto [resolution, integration_rule] = get(query, element.GetGeomType()); integrator.SetIntegrationRule(*integration_rule); return resolution; } @@ -176,13 +171,11 @@ export namespace mean_field::quadrature { "The divergence test element does not match the registered " "gravity potential." ); - const Query query = - GravityField::make_query( - role, transformation.OrderW(), {}, domain, mapping - ); + const Query query = GravityField::make_query( + role, transformation.OrderW(), {}, domain, mapping + ); - const auto [resolution, integration_rule] = - get(query, trial_element.GetGeomType()); + const auto [resolution, integration_rule] = get(query, trial_element.GetGeomType()); integrator.SetIntegrationRule(*integration_rule); return resolution; } @@ -200,12 +193,8 @@ export namespace mean_field::quadrature { "The boundary element does not match the registered gravity-flux " "normal trace." ); - const Query query = - GravityField::make_query( - role, 0, {}, domain, mapping - ); - const auto [resolution, integration_rule] = - get(query, boundary_element.GetGeomType()); + const Query query = GravityField::make_query(role, 0, {}, domain, mapping); + const auto [resolution, integration_rule] = get(query, boundary_element.GetGeomType()); integrator.SetIntegrationRule(*integration_rule); return resolution; } @@ -230,13 +219,11 @@ export namespace mean_field::quadrature { "The gravity-source coefficient order does not match the " "registered density field." ); - const Query query = - GravityField::make_query( - role, transformation.OrderW(), {}, domain, mapping - ); + const Query query = GravityField::make_query( + role, transformation.OrderW(), {}, domain, mapping + ); - const auto [resolution, integration_rule] = - get(query, test_element.GetGeomType()); + const auto [resolution, integration_rule] = get(query, test_element.GetGeomType()); integrator.SetIntegrationRule(*integration_rule); return resolution; } @@ -271,17 +258,14 @@ export namespace mean_field::quadrature { "gravity potential." ); MFEM_VERIFY( - coefficient_order == 0, - "The mapped gravity-source coefficient order must be zero; " - "density order is supplied by the registered trial field." + coefficient_order == 0, "The mapped gravity-source coefficient order must be zero; " + "density order is supplied by the registered trial field." + ); + const Query query = GravityField::make_query( + role, transformation.OrderW(), {}, domain, mapping ); - const Query query = - GravityField::make_query( - role, transformation.OrderW(), {}, domain, mapping - ); - const auto [resolution, integration_rule] = - get(query, transformation.GetGeometryType()); + const auto [resolution, integration_rule] = get(query, transformation.GetGeometryType()); integrator.SetIntRule(integration_rule); return resolution; } @@ -307,8 +291,7 @@ export namespace mean_field::quadrature { .geometry_weight_order = transformation.OrderW() }; - const auto [resolution, integration_rule] = - get(query, velocity_element.GetGeomType()); + const auto [resolution, integration_rule] = get(query, velocity_element.GetGeomType()); integrator.SetIntegrationRule(*integration_rule); return resolution; } @@ -323,8 +306,7 @@ export namespace mean_field::quadrature { const utils::DOMAINS domain, const MappingKind mapping ) const { - const auto [resolution, integration_rule] = - get(term, role, geometry, base_order, domain, mapping); + const auto [resolution, integration_rule] = get(term, role, geometry, base_order, domain, mapping); integrator.SetIntegrationRule(*integration_rule); return resolution; } diff --git a/libmeanfield/interface/quadrature/policy.cppm b/libmeanfield/interface/quadrature/policy.cppm index d2d529d..cb6e294 100644 --- a/libmeanfield/interface/quadrature/policy.cppm +++ b/libmeanfield/interface/quadrature/policy.cppm @@ -14,6 +14,7 @@ export namespace mean_field::quadrature { gravity_hdiv_mass, gravity_divergence, gravity_source, + gravity_force, gravity_boundary, centrifugal, density_projection, @@ -32,12 +33,7 @@ export namespace mean_field::quadrature { error_norm }; - enum class QuadratureRole { - discretization, - preconditioner, - diagnostic, - projection - }; + enum class QuadratureRole { discretization, preconditioner, diagnostic, projection }; enum class MappingKind { none, affine, general, kelvin }; @@ -59,6 +55,7 @@ export namespace mean_field::quadrature { RuleControl gravity_hdiv_mass; RuleControl gravity_divergence; RuleControl gravity_source; + RuleControl gravity_force; RuleControl gravity_boundary; RuleControl centrifugal; RuleControl density_projection; @@ -130,6 +127,7 @@ export namespace mean_field::quadrature { QuadratureTermOptions gravity_hdiv_mass; QuadratureTermOptions gravity_divergence; QuadratureTermOptions gravity_source; + QuadratureTermOptions gravity_force; QuadratureTermOptions gravity_boundary; QuadratureTermOptions centrifugal; QuadratureTermOptions density_projection; @@ -211,35 +209,20 @@ export namespace mean_field::quadrature { if (fixed_order.has_value()) { if (*fixed_order < 0) { - throw std::invalid_argument( - "Quadrature fixed order cannot be negative." - ); + throw std::invalid_argument("Quadrature fixed order cannot be negative."); } - return { - .base_order = base_order, - .boost = 0, - .order = *fixed_order, - .used_fixed_order = true - }; + return {.base_order = base_order, .boost = 0, .order = *fixed_order, .used_fixed_order = true}; } - const int boost = - rule_set.fallback.boost + role_control.boost + term_control.boost; + const int boost = rule_set.fallback.boost + role_control.boost + term_control.boost; const int order = base_order + boost; if (order < 0) { - throw std::invalid_argument( - "Resolved quadrature order cannot be negative." - ); + throw std::invalid_argument("Resolved quadrature order cannot be negative."); } - return { - .base_order = base_order, - .boost = boost, - .order = order, - .used_fixed_order = false - }; + return {.base_order = base_order, .boost = boost, .order = order, .used_fixed_order = false}; } const RuleControl &Policy::get_control(const Term term) const { switch (term) { @@ -249,6 +232,8 @@ export namespace mean_field::quadrature { return rule_set.gravity_divergence; case Term::gravity_source: return rule_set.gravity_source; + case Term::gravity_force: + return rule_set.gravity_force; case Term::gravity_boundary: return rule_set.gravity_boundary; case Term::centrifugal: @@ -289,18 +274,14 @@ export namespace mean_field::quadrature { int Policy::compute_base_order(const Query &query) { if (query.base_order.has_value()) { if (*query.base_order < 0) { - throw std::invalid_argument( - "Quadrature base order cannot be negative." - ); + throw std::invalid_argument("Quadrature base order cannot be negative."); } return *query.base_order; } - if (query.trial_order < 0 || query.test_order < 0 || - query.coefficient_order < 0 || query.geometry_weight_order < 0) { - throw std::invalid_argument( - "Quadrature query orders cannot be negative." - ); + if (query.trial_order < 0 || query.test_order < 0 || query.coefficient_order < 0 || + query.geometry_weight_order < 0) { + throw std::invalid_argument("Quadrature query orders cannot be negative."); } int trial_order = query.trial_order; @@ -308,12 +289,10 @@ export namespace mean_field::quadrature { trial_order = std::max(0, trial_order - 1); } - return trial_order + query.test_order + query.coefficient_order + - query.geometry_weight_order; + return trial_order + query.test_order + query.coefficient_order + query.geometry_weight_order; } - const RuleControl & - Policy::get_role_control(const QuadratureRole role) const { + const RuleControl &Policy::get_role_control(const QuadratureRole role) const { switch (role) { case QuadratureRole::discretization: return rule_set.roles.discretization; diff --git a/libmeanfield/interface/surface/isobaric.cppm b/libmeanfield/interface/surface/isobaric.cppm new file mode 100644 index 0000000..d5223bb --- /dev/null +++ b/libmeanfield/interface/surface/isobaric.cppm @@ -0,0 +1,64 @@ +module; + +#include +#include +#include + +export module mean_field:surface.isobaric; + +export import :surface.base; + +export namespace mean_field::surface { + class Isobaric final : public SurfaceBase { + public: + explicit Isobaric(const double targetPressure = 0.0) : m_targetPressure(targetPressure) { + validateTargetPressure(); + } + + [[nodiscard]] double targetPressure() const noexcept { + return m_targetPressure; + } + + [[nodiscard]] ResolvedSurfaceCondition + resolve(const mean_field::eos::EquationOfState &equationOfState) const override { + return ResolvedSurfaceCondition{resolveTargetEnthalpy(equationOfState)}; + } + + void validate(const mean_field::eos::EquationOfState &equationOfState) const override { + static_cast(resolveTargetEnthalpy(equationOfState)); + } + + private: + [[nodiscard]] double resolveTargetEnthalpy(const mean_field::eos::EquationOfState &equationOfState) const { + validateTargetPressure(); + + const double targetEnthalpy = equationOfState.enthalpy_from_pressure(m_targetPressure); + + if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) { + throw std::domain_error( + std::format( + "The equation of state resolved the isobaric " + "target P = {} to the invalid enthalpy h = {}.", + m_targetPressure, targetEnthalpy + ) + ); + } + + return targetEnthalpy; + } + + void validateTargetPressure() const { + if (!std::isfinite(m_targetPressure) || m_targetPressure < 0.0) { + throw std::invalid_argument( + std::format( + "The target surface pressure must be finite and " + "non-negative. Instead P = {} was provided.", + m_targetPressure + ) + ); + } + } + + double m_targetPressure; + }; +} // namespace mean_field::surface diff --git a/libmeanfield/interface/surface/surface_base.cppm b/libmeanfield/interface/surface/surface_base.cppm new file mode 100644 index 0000000..75a6b5b --- /dev/null +++ b/libmeanfield/interface/surface/surface_base.cppm @@ -0,0 +1,53 @@ +module; + +#include +#include + +export module mean_field:surface.base; + +export import :eos.base; + +export namespace mean_field::surface { + struct ResolvedSurfaceCondition final { + double targetEnthalpy{0.0}; + + explicit ResolvedSurfaceCondition(const double requestedTargetEnthalpy) + : targetEnthalpy(requestedTargetEnthalpy) { + if (!std::isfinite(targetEnthalpy) || targetEnthalpy < 0.0) { + throw std::invalid_argument( + "A resolved surface enthalpy must be finite and " + "non-negative." + ); + } + } + + [[nodiscard]] double residual(const double enthalpy) const { + if (!std::isfinite(enthalpy)) { + throw std::invalid_argument("A surface enthalpy value must be finite."); + } + + return enthalpy - targetEnthalpy; + } + + [[nodiscard]] static double jacobianAction(const double enthalpyVariation) { + if (!std::isfinite(enthalpyVariation)) { + throw std::invalid_argument("A surface enthalpy variation must be finite."); + } + + return enthalpyVariation; + } + }; + + class SurfaceBase { + public: + virtual ~SurfaceBase() = default; + + [[nodiscard]] virtual ResolvedSurfaceCondition + resolve(const mean_field::eos::EquationOfState &equationOfState) const = 0; + + virtual void validate(const mean_field::eos::EquationOfState &equationOfState) const = 0; + + protected: + SurfaceBase() = default; + }; +} // namespace mean_field::surface diff --git a/libmeanfield/interface/utils/blocks.cppm b/libmeanfield/interface/utils/blocks.cppm index 0321f64..13775dd 100644 --- a/libmeanfield/interface/utils/blocks.cppm +++ b/libmeanfield/interface/utils/blocks.cppm @@ -23,8 +23,7 @@ export namespace mean_field::utils::blocks { struct field { }; template struct block_row { }; - template - struct residual_block final : residual_block_base { + template struct residual_block final : residual_block_base { static constexpr int index = index_value; // ReSharper disable once CppNonExplicitConversionOperator @@ -110,43 +109,33 @@ export namespace mean_field::utils::blocks { template struct contains_type; - template - struct contains_type> : std::false_type { }; + template struct contains_type> : std::false_type { }; template struct contains_type> - : std::conditional_t< - std::is_same_v, - std::true_type, - contains_type>> { }; + : std::conditional_t, std::true_type, contains_type>> { }; - template - inline constexpr bool contains_type_v = contains_type::value; + template inline constexpr bool contains_type_v = contains_type::value; template struct type_count; - template - struct type_count> : std::integral_constant { }; + template struct type_count> : std::integral_constant { }; template struct type_count> : std::integral_constant< int, - (std::is_same_v ? 1 : 0) + - type_count>::value> { }; + (std::is_same_v ? 1 : 0) + type_count>::value> { }; - template - inline constexpr int type_count_v = type_count::value; + template inline constexpr int type_count_v = type_count::value; template struct types_are_unique; template struct types_are_unique> - : std::bool_constant< - ((type_count_v> == 1) && ...)> { }; + : std::bool_constant<((type_count_v> == 1) && ...)> { }; - template - inline constexpr bool types_are_unique_v = types_are_unique::value; + template inline constexpr bool types_are_unique_v = types_are_unique::value; template struct block_row_traits { using residual = void; @@ -156,8 +145,7 @@ export namespace mean_field::utils::blocks { static constexpr bool is_block_row = false; }; - template - struct block_row_traits> { + template struct block_row_traits> { using residual = Residual; using values = type_list; @@ -167,19 +155,15 @@ export namespace mean_field::utils::blocks { template struct type_index; - template - struct type_index> { + template struct type_index> { static constexpr int value = 0; }; - template - struct type_index> { - static constexpr int value = - 1 + type_index>::value; + template struct type_index> { + static constexpr int value = 1 + type_index>::value; }; - template - inline constexpr int type_index_v = type_index::value; + template inline constexpr int type_index_v = type_index::value; template struct block_form { using value_blocks = ValueBlocks; @@ -192,36 +176,22 @@ export namespace mean_field::utils::blocks { template struct block_form_is_valid : std::false_type { }; template - struct block_form_is_valid< - block_form, type_list>> + struct block_form_is_valid, type_list>> : std::bool_constant< (std::is_base_of_v && ...) && - (std::is_base_of_v && ...) && - types_are_unique_v> && + (std::is_base_of_v && ...) && types_are_unique_v> && types_are_unique_v>> { }; - template - inline constexpr bool block_form_is_valid_v = - block_form_is_valid::value; + template inline constexpr bool block_form_is_valid_v = block_form_is_valid::value; template struct block_row_is_valid : std::false_type { }; - template < - typename Residual, - typename... Values, - typename ValueBlocks, - typename ResidualBlocks> - struct block_row_is_valid< - block_row, - ValueBlocks, - ResidualBlocks> + template + struct block_row_is_valid, ValueBlocks, ResidualBlocks> : std::bool_constant< - std::is_base_of_v && - contains_type_v && - ((std::is_base_of_v && - contains_type_v) && - ...) && + std::is_base_of_v && contains_type_v && + ((std::is_base_of_v && contains_type_v) && ...) && types_are_unique_v>> { }; template struct row_residual_list; @@ -230,73 +200,50 @@ export namespace mean_field::utils::blocks { using type = type_list::residual...>; }; - template - using row_residual_list_t = typename row_residual_list::type; + template using row_residual_list_t = typename row_residual_list::type; - template - struct jacobian_form_is_valid : std::false_type { }; + template struct jacobian_form_is_valid : std::false_type { }; template - struct jacobian_form_is_valid< - block_form, type_list>, - type_list> { - using form_type = - block_form, type_list>; + struct jacobian_form_is_valid, type_list>, type_list> { + using form_type = block_form, type_list>; - using value_blocks = type_list; - using residual_blocks = type_list; - using rows = type_list; + using value_blocks = type_list; + using residual_blocks = type_list; + using rows = type_list; - static constexpr bool value = - block_form_is_valid_v && - (block_row_is_valid::value && - ...) && - std::is_same_v, residual_blocks>; + static constexpr bool value = block_form_is_valid_v && + (block_row_is_valid::value && ...) && + std::is_same_v, residual_blocks>; }; template - inline constexpr bool jacobian_form_is_valid_v = - jacobian_form_is_valid::value; + inline constexpr bool jacobian_form_is_valid_v = jacobian_form_is_valid::value; template concept valid_jacobian_form = jacobian_form_is_valid_v; - template - struct has_jacobian_coupling; + template struct has_jacobian_coupling; template - struct has_jacobian_coupling> - : std::false_type { }; + struct has_jacobian_coupling> : std::false_type { }; - template < - typename Residual, - typename Value, - typename RowResidual, - typename... RowValues, - typename... RemainingRows> - struct has_jacobian_coupling< - Residual, - Value, - type_list, RemainingRows...>> + template + struct has_jacobian_coupling, RemainingRows...>> : std::conditional_t< std::is_same_v, std::bool_constant<(std::is_same_v || ...)>, - has_jacobian_coupling< - Residual, - Value, - type_list>> { }; + has_jacobian_coupling>> { }; template - inline constexpr bool has_jacobian_coupling_v = - has_jacobian_coupling::value; + inline constexpr bool has_jacobian_coupling_v = has_jacobian_coupling::value; template < typename Form, typename Term> consteval auto get_value_block(const Term &) { - using value_type = typename Term::value; - constexpr int index = - type_index_v; + using value_type = typename Term::value; + constexpr int index = type_index_v; return value_block{}; } @@ -305,8 +252,7 @@ export namespace mean_field::utils::blocks { typename Term> consteval auto get_residual_block(const Term &) { using residual_type = typename Term::residual; - constexpr int index = - type_index_v; + constexpr int index = type_index_v; return residual_block{}; } @@ -320,32 +266,24 @@ export namespace mean_field::utils::blocks { int, Form::residual_block_count> &residual_sizes ) { - build_offsets( - m_value_offsets, value_sizes, typename Form::value_blocks{} - ); + build_offsets(m_value_offsets, value_sizes, typename Form::value_blocks{}); - build_offsets( - m_residual_offsets, residual_sizes, - typename Form::residual_blocks{} - ); + build_offsets(m_residual_offsets, residual_sizes, typename Form::residual_blocks{}); } template [[nodiscard]] int size(value_block) const { return m_value_offsets[index + 1] - m_value_offsets[index]; } - template - [[nodiscard]] int size(residual_block) const { + template [[nodiscard]] int size(residual_block) const { return m_residual_offsets[index + 1] - m_residual_offsets[index]; } - template - [[nodiscard]] int offset(value_block) const { + template [[nodiscard]] int offset(value_block) const { return m_value_offsets[index]; } - template - [[nodiscard]] int offset(residual_block) const { + template [[nodiscard]] int offset(residual_block) const { return m_residual_offsets[index]; } @@ -391,8 +329,7 @@ export namespace mean_field::utils::blocks { int block_index = 0; ((offsets[block_index + 1] = - offsets[block_index] + - resolve_block_size(requested_sizes[block_index]), + offsets[block_index] + resolve_block_size(requested_sizes[block_index]), ++block_index), ...); } @@ -463,10 +400,12 @@ export namespace mean_field::utils::blocks { enthalpy::specific::value, displacement::geometry::value>, - // R_d(d, h) + // R_d(rho, d, g, h) block_row< displacement::geometry::residual, + density::mass::value, displacement::geometry::value, + gravity::gradient::value, enthalpy::specific::value>, // R_h(h, Phi, d, C) @@ -483,6 +422,15 @@ export namespace mean_field::utils::blocks { density::mass::value, displacement::geometry::value>>; + // Columns: [d, h] + // Rows: [R_d] + using pressure_force_form = block_form< + type_list, + type_list>; + + using pressure_force_jacobian_form = type_list< + block_row>; + static_assert(valid_jacobian_form< gravity_field_form, gravity_jacobian_form>); diff --git a/libmeanfield/interface/utils/domain.cppm b/libmeanfield/interface/utils/domain.cppm index b481213..164a4d3 100644 --- a/libmeanfield/interface/utils/domain.cppm +++ b/libmeanfield/interface/utils/domain.cppm @@ -1,24 +1,1092 @@ module; + +#include #include +#include +#include +#include +#include +#include export module mean_field:utils.domain; -export import :fem; -export import :mapping.domain_mapper; -export import :boundary.contexts; -export namespace mean_field::utils { +export namespace mean_field::utils::domain { + struct Domain { }; - bool get_reference_point( - const fem::FEM &fem, - const mfem::Vector &x_phys_target, - mfem::Vector &x_ref - ); + struct Core final : public Domain { + static constexpr std::string_view name = "core"; + }; - double eval_grid_function_at_point( - const fem::FEM &fem, - const mfem::GridFunction &u, - const mfem::Vector &x, - mapping::COORDINATE_SPACE vspace = mapping::COORDINATE_SPACE::REFERENCE, - mapping::COORDINATE_SPACE rspace = mapping::COORDINATE_SPACE::PHYSICAL - ); -} // namespace mean_field::utils \ No newline at end of file + struct Envelope final : public Domain { + static constexpr std::string_view name = "envelope"; + }; + + struct Vacuum final : public Domain { + static constexpr std::string_view name = "vacuum"; + }; + + struct Boundary { }; + + struct StellarSurface final : public Boundary { + static constexpr std::string_view name = "stellar_surface"; + }; + + struct InfinitySurface final : public Boundary { + static constexpr std::string_view name = "infinity_surface"; + }; + + template + concept IsDomain = std::is_base_of_v; + + template + concept IsBoundary = std::is_base_of_v; + + template struct DomainSet { }; + + template constexpr bool is_domain_set_v = false; + + template constexpr bool is_domain_set_v> = true; + + template + concept IsDomainSet = is_domain_set_v; + + template + concept IsDomainOrSet = IsDomain || IsDomainSet; + + using Stellar = DomainSet; + using All = DomainSet; + + struct DomainRelation { }; + + template struct Inscribed final : public DomainRelation { + using inner_type = A; + using outer_type = B; + + static constexpr std::string_view name = "inscribed"; + }; + + template struct Connected final : public DomainRelation { + using domain_type = A; + + static constexpr std::string_view name = "connected"; + }; + + template + concept IsRelation = std::is_base_of_v; + + template struct Material { + using domain_type = D; + + static constexpr int id = Id; + }; + + template struct BoundaryAttribute { + using boundary_type = B; + + static constexpr int id = Id; + }; + + template constexpr bool is_material_v = false; + + template constexpr bool is_material_v> = true; + + template + concept IsMaterial = is_material_v; + + template constexpr bool is_boundary_attr_v = false; + + template constexpr bool is_boundary_attr_v> = true; + + template + concept IsBoundaryAttr = is_boundary_attr_v; + + struct MaterialDescriptor { + std::string_view name; + int id; + }; + + struct BoundaryDescriptor { + std::string_view name; + int id; + }; + + template + [[nodiscard]] + consteval bool material_ids_are_unique() noexcept { + constexpr std::array materialIds{MaterialTs::id...}; + + for (std::size_t firstIndex = 0; firstIndex < materialIds.size(); ++firstIndex) { + for (std::size_t secondIndex = firstIndex + 1; secondIndex < materialIds.size(); ++secondIndex) { + if (materialIds[firstIndex] == materialIds[secondIndex]) { + return false; + } + } + } + + return true; + } + + template + [[nodiscard]] + consteval bool boundary_ids_are_unique() noexcept { + constexpr std::array boundaryIds{BoundaryTs::id...}; + + for (std::size_t firstIndex = 0; firstIndex < boundaryIds.size(); ++firstIndex) { + for (std::size_t secondIndex = firstIndex + 1; secondIndex < boundaryIds.size(); ++secondIndex) { + if (boundaryIds[firstIndex] == boundaryIds[secondIndex]) { + return false; + } + } + } + + return true; + } + + template struct MaterialDomainsAreUnique; + + template <> struct MaterialDomainsAreUnique<> : std::true_type { }; + + template struct MaterialDomainsAreUnique : std::true_type { }; + + template + struct MaterialDomainsAreUnique + : std::bool_constant< + (!std::is_same_v && + ...) && + MaterialDomainsAreUnique::value> { }; + + template struct BoundaryTypesAreUnique; + + template <> struct BoundaryTypesAreUnique<> : std::true_type { }; + + template struct BoundaryTypesAreUnique : std::true_type { }; + + template + struct BoundaryTypesAreUnique + : std::bool_constant< + (!std::is_same_v && + ...) && + BoundaryTypesAreUnique::value> { }; + + template + concept HaveUniqueMaterialIds = material_ids_are_unique(); + + template + concept HaveUniqueMaterialDomains = MaterialDomainsAreUnique::value; + + template + concept HaveUniqueBoundaryIds = boundary_ids_are_unique(); + + template + concept HaveUniqueBoundaryTypes = BoundaryTypesAreUnique::value; + + template + requires(HaveUniqueMaterialIds && HaveUniqueMaterialDomains) + struct MaterialList { + static constexpr std::size_t count = sizeof...(MaterialTs); + + [[nodiscard]] + static constexpr std::array< + MaterialDescriptor, + count> descriptors() noexcept { + return {MaterialDescriptor{.name = MaterialTs::domain_type::name, .id = MaterialTs::id}...}; + } + }; + + template + requires(HaveUniqueBoundaryIds && HaveUniqueBoundaryTypes) + struct BoundaryList { + static constexpr std::size_t count = sizeof...(BoundaryTs); + + [[nodiscard]] + static constexpr std::array< + BoundaryDescriptor, + count> descriptors() noexcept { + return {BoundaryDescriptor{.name = BoundaryTs::boundary_type::name, .id = BoundaryTs::id}...}; + } + }; + + template struct DomainMaterialResolver; + + template + struct DomainMaterialResolver> { + static constexpr bool registered = (std::is_same_v || ...); + + [[nodiscard]] + static constexpr bool contains_attribute(int materialId) noexcept { + return ((std::is_same_v && MaterialTs::id == materialId) || ...); + } + }; + + template + struct DomainMaterialResolver, MaterialList> { + static constexpr bool registered = + (DomainMaterialResolver>::registered && ...); + + [[nodiscard]] + static constexpr bool contains_attribute(int materialId) noexcept { + return ( + DomainMaterialResolver>::contains_attribute(materialId) || ... + ); + } + }; + + template struct BoundaryAttributeResolver; + + template + struct BoundaryAttributeResolver> { + static constexpr bool registered = (std::is_same_v || ...); + + [[nodiscard]] + static constexpr bool matches_attribute(int boundaryId) noexcept { + return ( + (std::is_same_v && BoundaryTs::id == boundaryId) || ... + ); + } + + [[nodiscard]] + static consteval int attribute() { + static_assert( + registered, "Requested boundary is not registered " + "in this schema." + ); + + int result = 0; + + ((std::is_same_v ? result = BoundaryTs::id : result), ...); + + return result; + } + }; + + template constexpr bool is_material_list_v = false; + + template constexpr bool is_material_list_v> = true; + + template + concept IsMaterialList = is_material_list_v; + + template constexpr bool is_boundary_list_v = false; + + template constexpr bool is_boundary_list_v> = true; + + template + concept IsBoundaryList = is_boundary_list_v; + + template struct DomainOperandList { + static constexpr std::size_t count = sizeof...(DomainTs); + }; + + template + requires(sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2) + struct DomainBoundary final : public DomainRelation { + using boundary_type = BoundaryT; + + using domains_type = DomainOperandList; + + static constexpr std::size_t domainCount = sizeof...(DomainTs); + + static constexpr std::string_view name = "domain_boundary"; + }; + + template struct RelationList { + static constexpr std::size_t count = sizeof...(RelationTs); + }; + + template constexpr bool is_relation_list_v = false; + + template constexpr bool is_relation_list_v> = true; + + template + concept IsRelationList = is_relation_list_v; + + /* + * Compile-time validation that every semantic entity + * referenced by a relation is registered by the schema. + * + * Connected and Inscribed only reference domains. + * DomainBoundary references both a boundary and one or + * two domains. + */ + template + struct RelationUsesRegisteredEntities; + + template + struct RelationUsesRegisteredEntities, MaterialsT, BoundariesT> + : std::bool_constant::registered> { }; + + template + struct RelationUsesRegisteredEntities, MaterialsT, BoundariesT> + : std::bool_constant< + DomainMaterialResolver::registered && + DomainMaterialResolver::registered> { }; + + template + struct RelationUsesRegisteredEntities, MaterialsT, BoundariesT> + : std::bool_constant< + BoundaryAttributeResolver::registered && + (DomainMaterialResolver::registered && ...)> { }; + + template + struct RelationsUseRegisteredEntities; + + template + struct RelationsUseRegisteredEntities> + : std::bool_constant<(RelationUsesRegisteredEntities::value && ...)> { }; + + template + concept HaveValidRelationEntities = RelationsUseRegisteredEntities::value; + + template + requires HaveValidRelationEntities + struct DomainSchema { + using materials_type = Materials; + using boundaries_type = Boundaries; + using relations_type = Relations; + + static constexpr std::size_t materialCount = Materials::count; + + static constexpr std::size_t boundaryCount = Boundaries::count; + + static constexpr std::size_t relationCount = Relations::count; + + [[nodiscard]] + static constexpr auto materials() noexcept { + return Materials::descriptors(); + } + + [[nodiscard]] + static constexpr auto boundaries() noexcept { + return Boundaries::descriptors(); + } + + template + [[nodiscard]] + static consteval bool contains_domain() noexcept { + return DomainMaterialResolver::registered; + } + + template + [[nodiscard]] + static constexpr bool attribute_belongs_to(int materialId) noexcept { + static_assert( + contains_domain(), "Requested domain is not completely " + "registered in this schema." + ); + + return DomainMaterialResolver::contains_attribute(materialId); + } + + template + [[nodiscard]] + static consteval bool contains_boundary() noexcept { + return BoundaryAttributeResolver::registered; + } + + template + [[nodiscard]] + static consteval int boundary_attribute() noexcept { + return BoundaryAttributeResolver::attribute(); + } + + template + [[nodiscard]] + static constexpr bool boundary_attribute_matches(int boundaryId) noexcept { + static_assert( + contains_boundary(), "Requested boundary is not registered " + "in this schema." + ); + + return BoundaryAttributeResolver::matches_attribute(boundaryId); + } + }; + + template constexpr bool is_schema_v = false; + + template + constexpr bool is_schema_v> = true; + + template + concept IsSchema = is_schema_v; + + enum class RelationValidationFailure { + None, + + // Connected + DomainAbsent, + DomainDisconnected, + + // Inscribed + InnerDomainAbsent, + OuterDomainAbsent, + InnerDomainHasNoBoundary, + InnerDomainTouchesMeshBoundary, + InnerDomainTouchesUnexpectedMaterial, + + // DomainBoundary + DomainBoundaryAbsent, + DomainBoundaryTaggedFaceHasWrongTopology, + DomainBoundaryTaggedFaceTouchesUnexpectedMaterial, + DomainBoundaryExpectedFaceIsUntagged, + DomainBoundaryExpectedFaceHasWrongAttribute + }; + + struct RelationValidationResult { + RelationValidationFailure failure{RelationValidationFailure::None}; + + struct InscribedDiagnostics { + int faceId{-1}; + int innerElementId{-1}; + int adjacentElementId{-1}; + int adjacentMaterialId{-1}; + }; + + std::optional inscribedDiagnostics = std::nullopt; + + struct ConnectedDiagnostics { + int elementId{-1}; + int domainElementCount{0}; + int visitedElementCount{0}; + }; + + std::optional connectedDiagnostics = std::nullopt; + + struct DomainBoundaryDiagnostics { + int faceId{-1}; + int boundaryElementId{-1}; + + int expectedBoundaryAttribute{0}; + std::optional actualBoundaryAttribute = std::nullopt; + + int firstElementId{-1}; + int secondElementId{-1}; + + std::optional firstMaterialId = std::nullopt; + std::optional secondMaterialId = std::nullopt; + }; + + std::optional domainBoundaryDiagnostics = std::nullopt; + + [[nodiscard]] + bool valid() const noexcept { + return failure == RelationValidationFailure::None; + } + + [[nodiscard]] + explicit operator bool() const noexcept { + return valid(); + } + }; + + template struct RelationValidator; + + template struct RelationValidator> { + template + [[nodiscard]] + static RelationValidationResult validate(const mfem::Mesh &mesh) { + static_assert( + SchemaT::template contains_domain(), "The inner domain of Inscribed is not " + "registered in the supplied schema." + ); + + static_assert( + SchemaT::template contains_domain(), "The outer domain of Inscribed is not " + "registered in the supplied schema." + ); + + bool foundInnerElement = false; + bool foundOuterElement = false; + bool foundInnerBoundary = false; + + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const int materialId = mesh.GetAttribute(elementId); + + foundInnerElement = foundInnerElement || SchemaT::template attribute_belongs_to(materialId); + + foundOuterElement = foundOuterElement || SchemaT::template attribute_belongs_to(materialId); + } + + if (!foundInnerElement) { + return {.failure = RelationValidationFailure::InnerDomainAbsent}; + } + + if (!foundOuterElement) { + return {.failure = RelationValidationFailure::OuterDomainAbsent}; + } + + for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) { + int firstElementId = -1; + int secondElementId = -1; + + mesh.GetFaceElements(faceId, &firstElementId, &secondElementId); + + const bool firstIsInner = + firstElementId >= 0 && + SchemaT::template attribute_belongs_to(mesh.GetAttribute(firstElementId)); + + const bool secondIsInner = + secondElementId >= 0 && + SchemaT::template attribute_belongs_to(mesh.GetAttribute(secondElementId)); + + if (firstIsInner == secondIsInner) { + continue; + } + + foundInnerBoundary = true; + + const int innerElementId = firstIsInner ? firstElementId : secondElementId; + + const int adjacentElementId = firstIsInner ? secondElementId : firstElementId; + + if (adjacentElementId < 0) { + return { + .failure = RelationValidationFailure::InnerDomainTouchesMeshBoundary, + .inscribedDiagnostics = std::make_optional( + {.faceId = faceId, .innerElementId = innerElementId} + ) + }; + } + + const int adjacentMaterialId = mesh.GetAttribute(adjacentElementId); + + if (!SchemaT::template attribute_belongs_to(adjacentMaterialId)) { + return { + .failure = RelationValidationFailure::InnerDomainTouchesUnexpectedMaterial, + .inscribedDiagnostics = std::make_optional( + {.faceId = faceId, + .innerElementId = innerElementId, + .adjacentElementId = adjacentElementId, + .adjacentMaterialId = adjacentMaterialId} + ) + }; + } + } + + if (!foundInnerBoundary) { + return {.failure = RelationValidationFailure::InnerDomainHasNoBoundary}; + } + + return {}; + } + }; + + template struct RelationValidator> { + template + [[nodiscard]] + static RelationValidationResult validate(const mfem::Mesh &mesh) { + static_assert( + SchemaT::template contains_domain(), "Connected refers to a domain which is " + "not completely registered in the " + "supplied DomainSchema." + ); + + std::vector belongsToDomain(static_cast(mesh.GetNE()), false); + + int domainElementCount = 0; + int firstDomainElement = -1; + + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const int materialId = mesh.GetAttribute(elementId); + + const bool belongs = SchemaT::template attribute_belongs_to(materialId); + + belongsToDomain[static_cast(elementId)] = belongs; + + if (!belongs) { + continue; + } + + ++domainElementCount; + + if (firstDomainElement < 0) { + firstDomainElement = elementId; + } + } + + if (domainElementCount == 0) { + return { + .failure = RelationValidationFailure::DomainAbsent, + .connectedDiagnostics = std::make_optional( + {.domainElementCount = 0, .visitedElementCount = 0} + ) + }; + } + + std::vector> adjacency(static_cast(mesh.GetNE())); + + for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) { + int firstElementId = -1; + int secondElementId = -1; + + mesh.GetFaceElements(faceId, &firstElementId, &secondElementId); + + if (firstElementId < 0 || secondElementId < 0) { + continue; + } + + const bool firstBelongs = belongsToDomain[static_cast(firstElementId)]; + + const bool secondBelongs = belongsToDomain[static_cast(secondElementId)]; + + if (!(firstBelongs && secondBelongs)) { + continue; + } + + adjacency[static_cast(firstElementId)].push_back(secondElementId); + + adjacency[static_cast(secondElementId)].push_back(firstElementId); + } + + std::vector visited(static_cast(mesh.GetNE()), false); + + std::vector pending; + + pending.reserve(static_cast(domainElementCount)); + + pending.push_back(firstDomainElement); + + int visitedElementCount = 0; + + while (!pending.empty()) { + const int elementId = pending.back(); + + pending.pop_back(); + + if (visited[static_cast(elementId)]) { + continue; + } + + visited[static_cast(elementId)] = true; + + ++visitedElementCount; + + for (const int neighborElementId : adjacency[static_cast(elementId)]) { + if (!visited[static_cast(neighborElementId)]) { + pending.push_back(neighborElementId); + } + } + } + + if (visitedElementCount == domainElementCount) { + return { + .connectedDiagnostics = std::make_optional( + {.domainElementCount = domainElementCount, .visitedElementCount = visitedElementCount} + ) + }; + } + + int disconnectedElementId = -1; + + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const std::size_t index = static_cast(elementId); + + if (belongsToDomain[index] && !visited[index]) { + disconnectedElementId = elementId; + + break; + } + } + + return { + .failure = RelationValidationFailure::DomainDisconnected, + .connectedDiagnostics = std::make_optional( + {.elementId = disconnectedElementId, + .domainElementCount = domainElementCount, + .visitedElementCount = visitedElementCount} + ) + }; + } + }; + + template + struct RelationValidator> { + template + [[nodiscard]] + static RelationValidationResult validate(const mfem::Mesh &mesh) { + static_assert( + sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2, + "DomainBoundary requires exactly one or two domains." + ); + + static_assert( + SchemaT::template contains_boundary(), "DomainBoundary refers to a boundary which is not " + "registered in the supplied DomainSchema." + ); + + static_assert( + (SchemaT::template contains_domain() && ...), + "DomainBoundary refers to a domain which is not " + "completely registered in the supplied DomainSchema." + ); + + constexpr int expectedBoundaryAttribute = SchemaT::template boundary_attribute(); + + using DomainsTuple = std::tuple; + + /* + * Record all MFEM boundary elements associated with each + * mesh face. + * + * A face can in principle have more than one boundary + * element associated with it. We do not require exactly + * one here; instead, every boundary element on an expected + * face must carry the expected semantic boundary attribute. + */ + std::vector> boundaryElementsByFace(static_cast(mesh.GetNumFaces())); + + for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) { + const int faceId = mesh.GetBdrElementFaceIndex(boundaryElementId); + + if (faceId >= 0 && faceId < mesh.GetNumFaces()) { + boundaryElementsByFace[static_cast(faceId)].push_back(boundaryElementId); + } + } + + /* + * Build detailed diagnostics for one face. + */ + const auto make_diagnostics = [&mesh, expectedBoundaryAttribute]( + int faceId, int boundaryElementId, + std::optional actualBoundaryAttribute + ) { + RelationValidationResult::DomainBoundaryDiagnostics diagnostics{ + .faceId = faceId, + .boundaryElementId = boundaryElementId, + .expectedBoundaryAttribute = expectedBoundaryAttribute, + .actualBoundaryAttribute = actualBoundaryAttribute + }; + + if (faceId < 0 || faceId >= mesh.GetNumFaces()) { + return diagnostics; + } + + mesh.GetFaceElements(faceId, &diagnostics.firstElementId, &diagnostics.secondElementId); + + if (diagnostics.firstElementId >= 0) { + diagnostics.firstMaterialId = mesh.GetAttribute(diagnostics.firstElementId); + } + + if (diagnostics.secondElementId >= 0) { + diagnostics.secondMaterialId = mesh.GetAttribute(diagnostics.secondElementId); + } + + return diagnostics; + }; + + /* + * Check only the cardinality/topological shape required by + * the relation. + * + * One-domain form: + * + * Domain | computational exterior + * + * Exactly one adjacent volume element must exist. + * + * Two-domain form: + * + * DomainA | DomainB + * + * Both adjacent volume elements must exist. + */ + const auto has_required_topology = [](int firstElementId, int secondElementId) { + if constexpr (sizeof...(DomainTs) == 1) { + const bool firstExists = firstElementId >= 0; + + const bool secondExists = secondElementId >= 0; + + return firstExists != secondExists; + } else { + return firstElementId >= 0 && secondElementId >= 0; + } + }; + + /* + * Determine whether a face is exactly one of the faces + * described by DomainBoundary. + * + * For two domains, ordering is intentionally irrelevant. + */ + const auto face_matches_domains = [&mesh](int firstElementId, int secondElementId) { + if constexpr (sizeof...(DomainTs) == 1) { + using DomainT = std::tuple_element_t<0, DomainsTuple>; + + const bool firstExists = firstElementId >= 0; + + const bool secondExists = secondElementId >= 0; + + if (firstExists == secondExists) { + return false; + } + + const int elementId = firstExists ? firstElementId : secondElementId; + + const int materialId = mesh.GetAttribute(elementId); + + return SchemaT::template attribute_belongs_to(materialId); + } else { + using FirstDomainT = std::tuple_element_t<0, DomainsTuple>; + + using SecondDomainT = std::tuple_element_t<1, DomainsTuple>; + + if (firstElementId < 0 || secondElementId < 0) { + return false; + } + + const int firstMaterialId = mesh.GetAttribute(firstElementId); + + const int secondMaterialId = mesh.GetAttribute(secondElementId); + + const bool forwardMatch = SchemaT::template attribute_belongs_to(firstMaterialId) && + SchemaT::template attribute_belongs_to(secondMaterialId); + + const bool reverseMatch = SchemaT::template attribute_belongs_to(firstMaterialId) && + SchemaT::template attribute_belongs_to(secondMaterialId); + + return forwardMatch || reverseMatch; + } + }; + + bool foundTaggedBoundary = false; + + /* + * Forward validation: + * + * Every boundary element carrying BoundaryT must lie on + * exactly the topology/material interface declared by + * DomainBoundary. + */ + for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) { + const int boundaryAttribute = mesh.GetBdrAttribute(boundaryElementId); + + if (boundaryAttribute != expectedBoundaryAttribute) { + continue; + } + + foundTaggedBoundary = true; + + const int faceId = mesh.GetBdrElementFaceIndex(boundaryElementId); + + int firstElementId = -1; + int secondElementId = -1; + + mesh.GetFaceElements(faceId, &firstElementId, &secondElementId); + + if (!has_required_topology(firstElementId, secondElementId)) { + return { + .failure = RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology, + .domainBoundaryDiagnostics = + std::make_optional( + make_diagnostics(faceId, boundaryElementId, boundaryAttribute) + ) + }; + } + + if (!face_matches_domains(firstElementId, secondElementId)) { + return { + .failure = RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedMaterial, + .domainBoundaryDiagnostics = + std::make_optional( + make_diagnostics(faceId, boundaryElementId, boundaryAttribute) + ) + }; + } + } + + bool foundExpectedFace = false; + + /* + * Reverse validation: + * + * Every face having the declared domain adjacency must + * carry BoundaryT. + * + * This is important for physical constraints: a partially + * tagged Stellar/Vacuum interface must fail rather than + * silently leaving part of the stellar surface unconstrained. + */ + for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) { + int firstElementId = -1; + int secondElementId = -1; + + mesh.GetFaceElements(faceId, &firstElementId, &secondElementId); + + if (!face_matches_domains(firstElementId, secondElementId)) { + continue; + } + + foundExpectedFace = true; + + const auto &boundaryElementIds = boundaryElementsByFace[static_cast(faceId)]; + + if (boundaryElementIds.empty()) { + return { + .failure = RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged, + .domainBoundaryDiagnostics = + std::make_optional( + make_diagnostics(faceId, -1, std::nullopt) + ) + }; + } + + for (const int boundaryElementId : boundaryElementIds) { + const int actualBoundaryAttribute = mesh.GetBdrAttribute(boundaryElementId); + + if (actualBoundaryAttribute == expectedBoundaryAttribute) { + continue; + } + + return { + .failure = RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongAttribute, + .domainBoundaryDiagnostics = + std::make_optional( + make_diagnostics(faceId, boundaryElementId, actualBoundaryAttribute) + ) + }; + } + } + + /* + * If neither a correctly tagged boundary nor a face having + * the required semantic topology exists, the declared + * DomainBoundary simply is not realized by this mesh. + * + * In the usual partial-failure cases above we will already + * have returned a more specific diagnostic. + */ + if (!foundTaggedBoundary || !foundExpectedFace) { + return { + .failure = RelationValidationFailure::DomainBoundaryAbsent, + .domainBoundaryDiagnostics = + std::make_optional( + make_diagnostics(-1, -1, std::nullopt) + ) + }; + } + + return {}; + } + }; + + struct SchemaRelationValidationResult { + std::size_t relationIndex{0}; + std::string_view relationName; + RelationValidationResult result; + + [[nodiscard]] + bool valid() const noexcept { + return result.valid(); + } + + [[nodiscard]] + explicit operator bool() const noexcept { + return valid(); + } + }; + + struct SchemaValidationResult { + std::vector relationResults; + + [[nodiscard]] + bool valid() const noexcept { + for (const auto &relationResult : relationResults) { + if (!relationResult.valid()) { + return false; + } + } + + return true; + } + + [[nodiscard]] + explicit operator bool() const noexcept { + return valid(); + } + + [[nodiscard]] + std::size_t relation_count() const noexcept { + return relationResults.size(); + } + + [[nodiscard]] + std::size_t failed_relation_count() const noexcept { + std::size_t failureCount = 0; + + for (const auto &relationResult : relationResults) { + if (!relationResult.valid()) { + ++failureCount; + } + } + + return failureCount; + } + + [[nodiscard]] + std::size_t passed_relation_count() const noexcept { + return relationResults.size() - failed_relation_count(); + } + + [[nodiscard]] + std::optional first_failed_relation_index() const noexcept { + for (std::size_t relationIndex = 0; relationIndex < relationResults.size(); ++relationIndex) { + if (!relationResults[relationIndex].valid()) { + return relationIndex; + } + } + + return std::nullopt; + } + }; + + template struct SchemaRelationValidator; + + template + struct SchemaRelationValidator> { + [[nodiscard]] + static SchemaValidationResult validate(const mfem::Mesh &mesh) { + SchemaValidationResult schemaResult; + + schemaResult.relationResults.reserve(sizeof...(RelationTs)); + + std::size_t relationIndex = 0; + + (schemaResult.relationResults.push_back( + SchemaRelationValidationResult{ + .relationIndex = relationIndex++, + .relationName = RelationTs::name, + .result = RelationValidator::template validate(mesh) + } + ), + ...); + + return schemaResult; + } + }; + + template + [[nodiscard]] + SchemaValidationResult validate_schema(const mfem::Mesh &mesh) { + using RelationsT = typename SchemaT::relations_type; + + return SchemaRelationValidator::validate(mesh); + } + + using CoreEnvelopeVacuumDomainSchema = DomainSchema< + MaterialList, Material, Material>, + BoundaryList, BoundaryAttribute>, + RelationList< + // All Domains must be fully connected + Connected, + Connected, + Connected, + + // Describe the topology of the mesh (core must be within envelope and the stellar domain (core + envelope) + // must be inscribed within vacuum region + Inscribed, + Inscribed, + + // The stellar surface sits between the stellar and vacuum domain and the infinity surface sits at the + // outside of the vacuum domain + DomainBoundary, + DomainBoundary>>; +} // namespace mean_field::utils::domain \ No newline at end of file diff --git a/libmeanfield/interface/utils/misc.cppm b/libmeanfield/interface/utils/misc.cppm index e958c12..6f9e24b 100644 --- a/libmeanfield/interface/utils/misc.cppm +++ b/libmeanfield/interface/utils/misc.cppm @@ -66,15 +66,13 @@ export namespace mean_field::utils { [[maybe_unused]] constexpr int PORT = 19916; template - concept is_xad = std::is_same_v> || - std::is_same_v> || + concept is_xad = std::is_same_v> || std::is_same_v> || std::is_same_v>; template - concept is_real = std::is_floating_point_v || is_xad; + concept is_real = std::is_floating_point_v || is_xad; - template - using EOS_P = std::function; + template using EOS_P = std::function; enum class DOMAINS : uint8_t { CORE = 1 << 0, diff --git a/libmeanfield/interface/utils/user.cppm b/libmeanfield/interface/utils/user.cppm index 53ab587..7f0f251 100644 --- a/libmeanfield/interface/utils/user.cppm +++ b/libmeanfield/interface/utils/user.cppm @@ -32,8 +32,7 @@ export namespace mean_field::utils { double mass{}; double c{}; DomainMapperStatelessOptions domain_mapper_options{}; - mapping::compactification::options::KelvinCompactificationOptions - kelvin_options{}; + mapping::compactification::options::KelvinCompactificationOptions kelvin_options{}; int max_iters{}; double tol{}; diff --git a/tests/field/field_base.cpp b/tests/field/field_base.cpp new file mode 100644 index 0000000..f27ec54 --- /dev/null +++ b/tests/field/field_base.cpp @@ -0,0 +1,414 @@ +#include + +#include +#include +#include +#include + +import mean_field; +import test_helpers; + +namespace field_base_test_utils { + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + + using IndependentL2Scalar = field::ScalarQ>; + + using IndependentH1Scalar = field::ScalarQ>; + + using IndependentH1Vector = field::VectorQ>; + + using IndependentL2Vector = field::VectorQ>; + + using Potential = field::ScalarQ>; + + using Flux = field::VectorQ, field::Disc>; + + using CurlSource = field::VectorQ>; + + using CurlQuantity = field::VectorQ, field::Disc>; + + using SampleOperand = field::Operand; + + using SampleGradientOperand = field::Operand; + + using SampleForm = field::FormSpec<17, 2, SampleOperand, SampleGradientOperand>; + + struct StellarSupportedObject { + using Support = field::DomainSupport; + }; + + struct AllSupportedObject { + using Support = field::DomainSupport; + }; + + struct NonSpatialObject { + using Support = field::NonSpatialSupport; + }; +} // namespace field_base_test_utils + +TEST_CASE( + "Field Base Type Lists Track Compile Time Membership", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using List = field::TypeList; + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE_FALSE(field::typeListContains); + + STATIC_REQUIRE_FALSE(field::typeListContains>); + + CHECK(true); +} + +TEST_CASE( + "Field Base Function Space Tags Encode Supported Tensor Ranks", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + STATIC_REQUIRE(field::SpaceTag); + + STATIC_REQUIRE(field::SpaceTag); + + STATIC_REQUIRE(field::SpaceTag); + + STATIC_REQUIRE(field::SpaceTag); + + STATIC_REQUIRE_FALSE(field::SpaceTag); + + STATIC_REQUIRE(field::spaceSupportsRank); + + STATIC_REQUIRE(field::spaceSupportsRank); + + STATIC_REQUIRE(field::spaceSupportsRank); + + STATIC_REQUIRE(field::spaceSupportsRank); + + STATIC_REQUIRE_FALSE(field::spaceSupportsRank); + + STATIC_REQUIRE(field::spaceSupportsRank); + + STATIC_REQUIRE_FALSE(field::spaceSupportsRank); + + STATIC_REQUIRE(field::spaceSupportsRank); + + CHECK(field::L2::name == std::string_view{"L2"}); + + CHECK(field::H1::name == std::string_view{"H1"}); + + CHECK(field::RT::name == std::string_view{"RT"}); + + CHECK(field::ND::name == std::string_view{"ND"}); +} + +TEST_CASE( + "Field Base Discretization Descriptors Preserve Space And Family Order", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using L2Disc = field::Disc; + + using H1Disc = field::Disc; + + using RTDisc = field::Disc; + + using NDDisc = field::Disc; + + STATIC_REQUIRE(field::DiscretizationTag); + + STATIC_REQUIRE(field::DiscretizationTag); + + STATIC_REQUIRE(field::DiscretizationTag); + + STATIC_REQUIRE(field::DiscretizationTag); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(L2Disc::familyOrder == 2); + + STATIC_REQUIRE(H1Disc::familyOrder == 4); + + STATIC_REQUIRE(RTDisc::familyOrder == 1); + + STATIC_REQUIRE(NDDisc::familyOrder == 3); + + CHECK(true); +} + +TEST_CASE( + "Field Base Relations Preserve Their Source Quantities", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Source = field_base_test_utils::IndependentL2Scalar; + + using Gradient = field::FieldRelation::Gradient; + + using Divergence = field::FieldRelation::Divergence; + + using Curl = field::FieldRelation::Curl; + + STATIC_REQUIRE(field::ValidRelation); + + STATIC_REQUIRE(field::ValidRelation); + + STATIC_REQUIRE(field::ValidRelation); + + STATIC_REQUIRE(field::ValidRelation); + + STATIC_REQUIRE_FALSE(field::ValidRelation); + + STATIC_REQUIRE(field::IsGradient::value); + + STATIC_REQUIRE(field::IsDivergence::value); + + STATIC_REQUIRE(field::IsCurl::value); + + STATIC_REQUIRE(std::same_as::Type, Source>); + + STATIC_REQUIRE(std::same_as::Type, Source>); + + STATIC_REQUIRE(std::same_as::Type, Source>); + + STATIC_REQUIRE(std::same_as::Type, void>); + + CHECK(true); +} + +TEST_CASE( + "Field Base Finite Element Quantities Preserve Rank Storage Space And Order", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Scalar = field_base_test_utils::IndependentL2Scalar; + + using Vector = field_base_test_utils::IndependentH1Vector; + + STATIC_REQUIRE(field::FieldQuantity); + + STATIC_REQUIRE(field::FieldQuantity); + + STATIC_REQUIRE(field::RegisteredQuantity); + + STATIC_REQUIRE(field::RegisteredQuantity); + + STATIC_REQUIRE(Scalar::rankValue == 0); + + STATIC_REQUIRE(Vector::rankValue == 1); + + STATIC_REQUIRE(Scalar::familyOrder == 2); + + STATIC_REQUIRE(Vector::familyOrder == 3); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(Scalar::storageKind == field::StorageKind::finite_element); + + STATIC_REQUIRE(Vector::storageKind == field::StorageKind::finite_element); + + STATIC_REQUIRE(Scalar::staticBlockSize == field::dynamicBlockSize); + + STATIC_REQUIRE(Vector::staticBlockSize == field::dynamicBlockSize); + + CHECK(true); +} + +TEST_CASE( + "Field Base Global Scalars Are Registered But Are Not Finite Element Quantities", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + STATIC_REQUIRE(field::GlobalScalarQuantity); + + STATIC_REQUIRE(field::RegisteredQuantity); + + STATIC_REQUIRE_FALSE(field::FieldQuantity); + + STATIC_REQUIRE(field::GlobalScalarQ::rankValue == 0); + + STATIC_REQUIRE(field::GlobalScalarQ::storageKind == field::StorageKind::global_scalar); + + STATIC_REQUIRE(field::GlobalScalarQ::staticBlockSize == 1); + + STATIC_REQUIRE(std::same_as); + + CHECK(true); +} + +TEST_CASE( + "Field Base Derived Quantity Detection Follows Physical Relations", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Independent = field_base_test_utils::IndependentL2Scalar; + + using Flux = field_base_test_utils::Flux; + + using CurlQuantity = field_base_test_utils::CurlQuantity; + + STATIC_REQUIRE_FALSE(field::DerivedQuantity); + + STATIC_REQUIRE(field::DerivedQuantity); + + STATIC_REQUIRE(field::DerivedQuantity); + + STATIC_REQUIRE(std::same_as, field_base_test_utils::Potential>); + + STATIC_REQUIRE(std::same_as, field_base_test_utils::CurlSource>); + + CHECK(true); +} + +TEST_CASE( + "Field Base RT L2 Constraint Accepts The Registered Stable Pair Contract", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Potential = field_base_test_utils::Potential; + + using Flux = field_base_test_utils::Flux; + + using Constraint = field::RtL2StablePair; + + STATIC_REQUIRE(field::validate_constraints(field::TypeList{})); + + STATIC_REQUIRE(field::validate_constraints(field::TypeList<>{})); + + CHECK(true); +} + +TEST_CASE( + "Field Base Operations And Operands Preserve Mathematical Intent", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Quantity = field_base_test_utils::IndependentH1Scalar; + + using ValueOperand = field::Operand; + + using GradientOperand = field::Operand; + + STATIC_REQUIRE(field::FieldOperationTag); + + STATIC_REQUIRE(field::FieldOperationTag); + + STATIC_REQUIRE(field::FieldOperationTag); + + STATIC_REQUIRE(field::FieldOperationTag); + + STATIC_REQUIRE(field::FieldOperationTag); + + STATIC_REQUIRE_FALSE(field::FieldOperationTag); + + STATIC_REQUIRE(field::FieldOperand); + + STATIC_REQUIRE(field::FieldOperand); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + CHECK(true); +} + +TEST_CASE( + "Field Base Form Specifications Preserve Policy Dynamic Orders And Operands", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using Form = field_base_test_utils::SampleForm; + + STATIC_REQUIRE(field::FieldForm); + + STATIC_REQUIRE(Form::policyKey == 17); + + STATIC_REQUIRE(Form::dynamicOrderCount == 2); + + STATIC_REQUIRE( + std::same_as< + typename Form::Operands, + field::TypeList> + ); + + STATIC_REQUIRE( + field::isRegisteredQuantityList> + ); + + STATIC_REQUIRE_FALSE(field::isRegisteredQuantityList>); + + STATIC_REQUIRE(field::isFieldFormList>); + + STATIC_REQUIRE(field::isFieldFormList>); + + STATIC_REQUIRE_FALSE(field::isFieldFormList>); + + CHECK(true); +} + +TEST_CASE( + "Field Base Support Types Distinguish Domain And Non Spatial Fields", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + + using StellarSupport = field::DomainSupport; + + using AllSupport = field::DomainSupport; + + STATIC_REQUIRE(field::IsFieldSupport); + + STATIC_REQUIRE(field::IsFieldSupport); + + STATIC_REQUIRE(field::IsFieldSupport); + + STATIC_REQUIRE(field::IsDomainSupport); + + STATIC_REQUIRE(field::IsDomainSupport); + + STATIC_REQUIRE_FALSE(field::IsDomainSupport); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE_FALSE(field::DomainSupportedField); + + STATIC_REQUIRE(field::NonSpatialField); + + STATIC_REQUIRE_FALSE(field::NonSpatialField); + + STATIC_REQUIRE(std::same_as, StellarSupport>); + + STATIC_REQUIRE(std::same_as, domain::Stellar>); + + CHECK(true); +} \ No newline at end of file diff --git a/tests/field/field_dof_map.cpp b/tests/field/field_dof_map.cpp new file mode 100644 index 0000000..9a9a9a8 --- /dev/null +++ b/tests/field/field_dof_map.cpp @@ -0,0 +1,724 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +import mean_field; +import test_helpers; + +namespace field_dof_map_test_utils { + namespace field = mean_field::field; + + namespace domain = mean_field::utils::domain; + + using Schema = domain::CoreEnvelopeVacuumDomainSchema; + + [[nodiscard]] + mfem::Array make_array(const std::initializer_list values) { + mfem::Array result(static_cast(values.size())); + + int index = 0; + + for (const int value : values) { + result[index++] = value; + } + + return result; + } + + [[nodiscard]] + mfem::Mesh make_split_mesh( + const int stellarAttribute = 2, + const int vacuumAttribute = 3 + ) { + int communicatorSize = 1; + + MPI_Comm_size(MPI_COMM_WORLD, &communicatorSize); + + /* + * Ensure there are enough cells that every reasonable MPI test + * configuration has useful work available. + */ + const int xElementCount = std::max(4, 2 * communicatorSize); + + constexpr int yElementCount = 2; + + mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D( + xElementCount, yElementCount, mfem::Element::QUADRILATERAL, true, static_cast(xElementCount), + static_cast(yElementCount) + ); + + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const int xIndex = elementId % xElementCount; + + mesh.GetElement(elementId)->SetAttribute(xIndex < xElementCount / 2 ? stellarAttribute : vacuumAttribute); + } + + mesh.SetAttributes(); + + return mesh; + } + + [[nodiscard]] + long long global_sum(const int localValue) { + const long long local = static_cast(localValue); + + long long global = 0; + + MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD); + + return global; + } + + template + concept CanMakeFieldDofMap = + requires(const mfem::ParFiniteElementSpace &space) { field::make_field_dof_map(space); }; + + using AlternateSchema = domain::DomainSchema< + domain::MaterialList< + domain::Material, + domain::Material, + domain::Material>, + domain::BoundaryList<>, + domain::RelationList<>>; +} // namespace field_dof_map_test_utils + +TEST_CASE( + "Field DOF Map Preserves Canonical Bidirectional Indexing", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const mfem::Array active = field_dof_map_test_utils::make_array({0, 2, 5, 7}); + + const field::FieldDofMap map(9, active); + + CHECK(map.full_size() == 9); + + CHECK(map.reduced_size() == 4); + + CHECK(map.inactive_size() == 5); + + CHECK_FALSE(map.is_identity()); + + CHECK(map.true_dof(0) == 0); + + CHECK(map.true_dof(1) == 2); + + CHECK(map.true_dof(2) == 5); + + CHECK(map.true_dof(3) == 7); + + REQUIRE(map.reduced_dof(0).has_value()); + + REQUIRE(map.reduced_dof(2).has_value()); + + REQUIRE(map.reduced_dof(5).has_value()); + + REQUIRE(map.reduced_dof(7).has_value()); + + CHECK(*map.reduced_dof(0) == 0); + + CHECK(*map.reduced_dof(2) == 1); + + CHECK(*map.reduced_dof(5) == 2); + + CHECK(*map.reduced_dof(7) == 3); + + CHECK_FALSE(map.reduced_dof(1).has_value()); + + CHECK_FALSE(map.reduced_dof(3).has_value()); + + CHECK(map.contains_true_dof(0)); + + CHECK(map.contains_true_dof(2)); + + CHECK_FALSE(map.contains_true_dof(1)); + + const mfem::Array &forward = map.reduced_to_true(); + + const mfem::Array &inverse = map.true_to_reduced(); + + REQUIRE(forward.Size() == 4); + + REQUIRE(inverse.Size() == 9); + + CHECK(forward[0] == 0); + CHECK(forward[1] == 2); + CHECK(forward[2] == 5); + CHECK(forward[3] == 7); + + CHECK(inverse[0] == 0); + CHECK(inverse[1] == -1); + CHECK(inverse[2] == 1); + CHECK(inverse[3] == -1); + CHECK(inverse[4] == -1); + CHECK(inverse[5] == 2); + CHECK(inverse[6] == -1); + CHECK(inverse[7] == 3); + CHECK(inverse[8] == -1); +} + +TEST_CASE( + "Field DOF Map Rejects Invalid Canonical Mappings", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const mfem::Array empty; + + CHECK_THROWS_AS((field::FieldDofMap(-1, empty)), std::invalid_argument); + + CHECK_THROWS_AS((field::FieldDofMap(4, field_dof_map_test_utils::make_array({-1, 2}))), std::invalid_argument); + + CHECK_THROWS_AS((field::FieldDofMap(4, field_dof_map_test_utils::make_array({1, 4}))), std::invalid_argument); + + /* + * Duplicate true DOF. + */ + CHECK_THROWS_AS((field::FieldDofMap(5, field_dof_map_test_utils::make_array({1, 1, 3}))), std::invalid_argument); + + /* + * Non-canonical unsorted ordering. + */ + CHECK_THROWS_AS((field::FieldDofMap(5, field_dof_map_test_utils::make_array({1, 3, 2}))), std::invalid_argument); +} + +TEST_CASE( + "Field DOF Map Rejects Out Of Range Index Queries", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3})); + + CHECK_THROWS_AS(map.true_dof(-1), std::out_of_range); + + CHECK_THROWS_AS(map.true_dof(2), std::out_of_range); + + CHECK_THROWS_AS(map.reduced_dof(-1), std::out_of_range); + + CHECK_THROWS_AS(map.reduced_dof(5), std::out_of_range); + + CHECK_THROWS_AS(map.contains_true_dof(-1), std::out_of_range); + + CHECK_THROWS_AS(map.contains_true_dof(5), std::out_of_range); +} + +TEST_CASE( + "Field DOF Map Gather Selects Exactly The Active True DOFs", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 3, 5})); + + mfem::Vector full(6); + + for (int trueDof = 0; trueDof < full.Size(); ++trueDof) { + full(trueDof) = 10.0 + static_cast(trueDof); + } + + const mfem::Vector reduced = map.gather(full); + + REQUIRE(reduced.Size() == 3); + + CHECK(reduced(0) == 11.0); + + CHECK(reduced(1) == 13.0); + + CHECK(reduced(2) == 15.0); + + mfem::Vector output(3); + + map.gather(full, output); + + CHECK(output(0) == 11.0); + + CHECK(output(1) == 13.0); + + CHECK(output(2) == 15.0); +} + +TEST_CASE( + "Field DOF Map Scatter Produces The Canonical Supported Projection", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 3, 5})); + + mfem::Vector reduced(3); + + reduced(0) = 2.0; + reduced(1) = 4.0; + reduced(2) = 6.0; + + const mfem::Vector full = map.scatter(reduced); + + REQUIRE(full.Size() == 6); + + CHECK(full(0) == 0.0); + CHECK(full(1) == 2.0); + CHECK(full(2) == 0.0); + CHECK(full(3) == 4.0); + CHECK(full(4) == 0.0); + CHECK(full(5) == 6.0); + + const mfem::Vector roundTrip = map.gather(full); + + REQUIRE(roundTrip.Size() == reduced.Size()); + + for (int index = 0; index < reduced.Size(); ++index) { + CHECK(roundTrip(index) == reduced(index)); + } +} + +TEST_CASE( + "Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(7, field_dof_map_test_utils::make_array({0, 2, 3, 6})); + + mfem::Vector original(7); + + for (int index = 0; index < original.Size(); ++index) { + original(index) = 0.25 + static_cast(index); + } + + const mfem::Vector reduced = map.gather(original); + + const mfem::Vector projected = map.scatter(reduced); + + for (int trueDof = 0; trueDof < original.Size(); ++trueDof) { + CAPTURE(trueDof); + + if (map.contains_true_dof(trueDof)) { + CHECK(projected(trueDof) == original(trueDof)); + } else { + CHECK(projected(trueDof) == 0.0); + } + } +} + +TEST_CASE( + "Field DOF Map Scatter Into Preserves Unsupported True DOFs", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 4})); + + mfem::Vector reduced(2); + + reduced(0) = 7.0; + reduced(1) = 9.0; + + mfem::Vector full(6); + + full = -3.0; + + map.scatter_into(reduced, full); + + CHECK(full(0) == -3.0); + CHECK(full(1) == 7.0); + CHECK(full(2) == -3.0); + CHECK(full(3) == -3.0); + CHECK(full(4) == 9.0); + CHECK(full(5) == -3.0); +} + +TEST_CASE( + "Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({0, 2, 4})); + + mfem::Vector reduced(3); + + reduced(0) = 1.0; + reduced(1) = 2.0; + reduced(2) = 3.0; + + mfem::Vector full(5); + + full = 10.0; + + map.scatter_add(reduced, full, 2.0); + + CHECK(full(0) == 12.0); + CHECK(full(1) == 10.0); + CHECK(full(2) == 14.0); + CHECK(full(3) == 10.0); + CHECK(full(4) == 16.0); +} + +TEST_CASE( + "Field DOF Map Operations Support MFEM Vector Views Without Resizing", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3})); + + mfem::Vector storage(9); + + storage = -8.0; + + /* + * View [2, 7) of the parent vector. + */ + mfem::Vector fullView(storage.GetData() + 2, 5); + + mfem::Vector reduced(2); + + reduced(0) = 4.0; + reduced(1) = 6.0; + + map.scatter_into(reduced, fullView); + + /* + * Storage outside the view must remain untouched. + */ + CHECK(storage(0) == -8.0); + CHECK(storage(1) == -8.0); + CHECK(storage(7) == -8.0); + CHECK(storage(8) == -8.0); + + /* + * Within the view, only active true DOFs change. + */ + CHECK(storage(2) == -8.0); + CHECK(storage(3) == 4.0); + CHECK(storage(4) == -8.0); + CHECK(storage(5) == 6.0); + CHECK(storage(6) == -8.0); +} + +TEST_CASE( + "Field DOF Map Operations Reject Incompatible Vector Sizes", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3})); + + mfem::Vector correctFull(5); + mfem::Vector wrongFull(4); + + mfem::Vector correctReduced(2); + mfem::Vector wrongReduced(3); + + CHECK_THROWS_AS(map.gather(wrongFull), std::invalid_argument); + + CHECK_THROWS_AS(map.gather(correctFull, wrongReduced), std::invalid_argument); + + CHECK_THROWS_AS(map.scatter(wrongReduced), std::invalid_argument); + + CHECK_THROWS_AS(map.scatter(correctReduced, wrongFull), std::invalid_argument); + + CHECK_THROWS_AS(map.scatter_into(wrongReduced, correctFull), std::invalid_argument); + + CHECK_THROWS_AS(map.scatter_add(correctReduced, wrongFull), std::invalid_argument); +} + +TEST_CASE( + "Field DOF Map Identity Mapping Is An Exact Vector Identity", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const field::FieldDofMap map(4, field_dof_map_test_utils::make_array({0, 1, 2, 3})); + + REQUIRE(map.is_identity()); + + REQUIRE(map.inactive_size() == 0); + + mfem::Vector full(4); + + full(0) = 0.1; + full(1) = -0.2; + full(2) = 3.7; + full(3) = 8.1; + + const mfem::Vector reduced = map.gather(full); + + const mfem::Vector restored = map.scatter(reduced); + + for (int index = 0; index < full.Size(); ++index) { + CHECK(reduced(index) == full(index)); + + CHECK(restored(index) == full(index)); + } +} + +TEST_CASE( + "Field DOF Map Validates Field DOF Support Consistency", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + field::FieldDofSupport support; + + support.activeTrueDofMarker.SetSize(5); + + support.activeTrueDofMarker = 0; + + support.activeTrueDofMarker[1] = 1; + + support.activeTrueDofMarker[3] = 1; + + support.activeTrueDofs = field_dof_map_test_utils::make_array({1, 3}); + + const field::FieldDofMap validMap(support); + + CHECK(validMap.full_size() == 5); + + CHECK(validMap.reduced_size() == 2); + + /* + * Make the marker disagree with the list. + */ + support.activeTrueDofMarker[3] = 0; + + CHECK_THROWS_AS((field::FieldDofMap(support)), std::invalid_argument); +} + +TEST_CASE( + "Field DOF Map Factory Is Available Only For Spatial Registered Fields", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap); + + STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap); + + STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap); + + STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap); + + STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofMap); + + CHECK(true); +} + +TEST_CASE( + "Field DOF Map Factory Exactly Preserves Density Support", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto finiteElementSpace = field::Field::make_fespace(mesh, *fec); + + REQUIRE(finiteElementSpace != nullptr); + + const field::FieldDofSupport support = + field::resolve_field_dof_support(*finiteElementSpace); + + const field::FieldDofMap map = + field::make_field_dof_map(*finiteElementSpace); + + REQUIRE(map.full_size() == finiteElementSpace->GetTrueVSize()); + + REQUIRE(map.reduced_size() == support.activeTrueDofs.Size()); + + REQUIRE(map.full_size() == support.activeTrueDofMarker.Size()); + + for (int reducedDof = 0; reducedDof < map.reduced_size(); ++reducedDof) { + CAPTURE(reducedDof); + + CHECK(map.true_dof(reducedDof) == support.activeTrueDofs[reducedDof]); + } + + for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) { + CAPTURE(trueDof); + + CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0)); + } + + const long long globalFullSize = field_dof_map_test_utils::global_sum(map.full_size()); + + const long long globalReducedSize = field_dof_map_test_utils::global_sum(map.reduced_size()); + + /* + * L2 density has independent vacuum element DOFs, so removing vacuum + * support must genuinely reduce the global nonlinear block. + */ + CHECK(globalReducedSize > 0); + + CHECK(globalReducedSize < globalFullSize); +} + +TEST_CASE( + "Field DOF Map Factory Exactly Preserves H1 Enthalpy Support", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto finiteElementSpace = field::Field::make_fespace(mesh, *fec); + + REQUIRE(finiteElementSpace != nullptr); + + const field::FieldDofSupport support = + field::resolve_field_dof_support(*finiteElementSpace); + + const field::FieldDofMap map = + field::make_field_dof_map(*finiteElementSpace); + + REQUIRE(map.reduced_size() == support.activeTrueDofs.Size()); + + for (int reducedDof = 0; reducedDof < map.reduced_size(); ++reducedDof) { + CHECK(map.true_dof(reducedDof) == support.activeTrueDofs[reducedDof]); + } + + /* + * The separate field_mfem support tests already establish that shared + * Stellar/Vacuum H1 trace DOFs are active. This test establishes that + * FieldDofMap preserves that active set exactly, rather than applying + * a second reduction or reinterpretation. + */ + for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) { + CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0)); + } + + const long long globalFullSize = field_dof_map_test_utils::global_sum(map.full_size()); + + const long long globalReducedSize = field_dof_map_test_utils::global_sum(map.reduced_size()); + + CHECK(globalReducedSize > 0); + + CHECK(globalReducedSize < globalFullSize); +} + +TEST_CASE( + "Field DOF Map Factory Produces Identity Maps For All Supported Fields", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto finiteElementSpace = field::Field::make_fespace(mesh, *fec); + + REQUIRE(finiteElementSpace != nullptr); + + const field::FieldDofMap map = + field::make_field_dof_map(*finiteElementSpace); + + CHECK(map.is_identity()); + + CHECK(map.full_size() == finiteElementSpace->GetTrueVSize()); + + CHECK(map.reduced_size() == finiteElementSpace->GetTrueVSize()); + + CHECK(map.inactive_size() == 0); + + for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) { + CHECK(map.true_dof(trueDof) == trueDof); + + CHECK(map.contains_true_dof(trueDof)); + } +} + +TEST_CASE( + "Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(17, 29); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto finiteElementSpace = field::Field::make_fespace(mesh, *fec); + + REQUIRE(finiteElementSpace != nullptr); + + const field::FieldDofMap map = + field::make_field_dof_map(*finiteElementSpace); + + const field::FieldDofSupport support = + field::resolve_field_dof_support( + *finiteElementSpace + ); + + CHECK(map.full_size() == support.activeTrueDofMarker.Size()); + + CHECK(map.reduced_size() == support.activeTrueDofs.Size()); + + for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) { + CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0)); + } +} + +TEST_CASE( + "Field DOF Map Reduced Vectors Round Trip Through Real Field Support", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto finiteElementSpace = field::Field::make_fespace(mesh, *fec); + + REQUIRE(finiteElementSpace != nullptr); + + const field::FieldDofMap map = + field::make_field_dof_map(*finiteElementSpace); + + mfem::Vector reduced(map.reduced_size()); + + for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) { + reduced(reducedDof) = 0.125 + 0.031 * static_cast(reducedDof + 1); + } + + const mfem::Vector full = map.scatter(reduced); + + const mfem::Vector recovered = map.gather(full); + + REQUIRE(recovered.Size() == reduced.Size()); + + for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) { + CAPTURE(reducedDof); + + CHECK(recovered(reducedDof) == reduced(reducedDof)); + } + + for (int trueDof = 0; trueDof < full.Size(); ++trueDof) { + if (!map.contains_true_dof(trueDof)) { + CHECK(full(trueDof) == 0.0); + } + } +} \ No newline at end of file diff --git a/tests/field/field_mfem.cpp b/tests/field/field_mfem.cpp new file mode 100644 index 0000000..44929c4 --- /dev/null +++ b/tests/field/field_mfem.cpp @@ -0,0 +1,778 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +import mean_field; +import test_helpers; + +namespace field_mfem_test_utils { + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + namespace quadrature = mean_field::quadrature; + + using Schema = domain::CoreEnvelopeVacuumDomainSchema; + + struct VectorL2Field { + static constexpr std::string_view name = "test_vector_l2"; + + using Support = field::DomainSupport; + + struct Vector final : field::VectorQ> { }; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList<>; + + static constexpr bool constraintsAreValid = field::validate_constraints(Constraints{}); + + static_assert(constraintsAreValid); + }; + + struct NdField { + static constexpr std::string_view name = "test_nd"; + + using Support = field::DomainSupport; + + struct Vector final : field::VectorQ> { }; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList<>; + + static constexpr bool constraintsAreValid = field::validate_constraints(Constraints{}); + + static_assert(constraintsAreValid); + }; + + using AlternateSchema = domain::DomainSchema< + domain::MaterialList< + domain::Material, + domain::Material, + domain::Material>, + domain::BoundaryList<>, + domain::RelationList<>>; + + template + concept CanResolveLocalSupport = requires(const mfem::FiniteElementSpace &space) { + field::resolve_field_local_dof_support(space); + }; + + [[nodiscard]] + mfem::Mesh make_two_domain_mesh( + const int leftAttribute = 2, + const int rightAttribute = 3 + ) { + mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D(2, 1, mfem::Element::QUADRILATERAL, true, 2.0, 1.0); + + mesh.GetElement(0)->SetAttribute(leftAttribute); + + mesh.GetElement(1)->SetAttribute(rightAttribute); + + mesh.SetAttributes(); + + return mesh; + } + + [[nodiscard]] + mfem::Mesh make_parallel_split_mesh() { + constexpr int xElementCount = 4; + constexpr int yElementCount = 2; + + mfem::Mesh mesh = + mfem::Mesh::MakeCartesian2D(xElementCount, yElementCount, mfem::Element::QUADRILATERAL, true, 4.0, 2.0); + + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const int xIndex = elementId % xElementCount; + + const int attribute = xIndex < 2 ? 2 : 3; + + mesh.GetElement(elementId)->SetAttribute(attribute); + } + + mesh.SetAttributes(); + + return mesh; + } + + [[nodiscard]] + std::vector decoded_element_vdofs( + const mfem::FiniteElementSpace &space, + const int elementId + ) { + mfem::Array signedVDofs; + + space.GetElementVDofs(elementId, signedVDofs); + + std::vector result; + + result.reserve(static_cast(signedVDofs.Size())); + + for (int index = 0; index < signedVDofs.Size(); ++index) { + result.push_back(mfem::FiniteElementSpace::DecodeDof(signedVDofs[index])); + } + + std::ranges::sort(result); + + result.erase(std::unique(result.begin(), result.end()), result.end()); + + return result; + } + + [[nodiscard]] + bool contains( + const mfem::Array &values, + const int value + ) { + for (int index = 0; index < values.Size(); ++index) { + if (values[index] == value) { + return true; + } + } + + return false; + } + + [[nodiscard]] + std::vector intersection( + const std::vector &first, + const std::vector &second + ) { + std::vector result; + + std::set_intersection(first.begin(), first.end(), second.begin(), second.end(), std::back_inserter(result)); + + return result; + } + + [[nodiscard]] + std::vector difference( + const std::vector &first, + const std::vector &second + ) { + std::vector result; + + std::set_difference(first.begin(), first.end(), second.begin(), second.end(), std::back_inserter(result)); + + return result; + } + + [[nodiscard]] + long long global_sum(const int localValue) { + const long long local = static_cast(localValue); + + long long global = 0; + + MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD); + + return global; + } +} // namespace field_mfem_test_utils + +TEST_CASE( + "Field MFEM Support Resolution Is Available Only For Domain Supported Fields", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + STATIC_REQUIRE(field::MfemDomainField); + + STATIC_REQUIRE(field::MfemDomainField); + + STATIC_REQUIRE(field::MfemDomainField); + + STATIC_REQUIRE(field::MfemDomainField); + + STATIC_REQUIRE_FALSE(field::MfemDomainField); + + STATIC_REQUIRE(field_mfem_test_utils::CanResolveLocalSupport); + + STATIC_REQUIRE_FALSE(field_mfem_test_utils::CanResolveLocalSupport); + + CHECK(true); +} + +TEST_CASE( + "Field MFEM Creates The Registered Finite Element Collection Families", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + using DensityField = field::Field; + + using GravityField = field::Field; + + using DisplacementField = field::Field; + + using EnthalpyField = field::Field; + + auto densityCollection = DensityField::make_fec(3); + + auto potentialCollection = GravityField::make_fec(3); + + auto fluxCollection = GravityField::make_fec(3); + + auto displacementCollection = DisplacementField::make_fec(3); + + auto enthalpyCollection = EnthalpyField::make_fec(3); + + auto vectorL2Collection = + field::Field::make_fec(3); + + auto ndCollection = + field::Field::make_fec(3); + + REQUIRE(densityCollection != nullptr); + + REQUIRE(potentialCollection != nullptr); + + REQUIRE(fluxCollection != nullptr); + + REQUIRE(displacementCollection != nullptr); + + REQUIRE(enthalpyCollection != nullptr); + + REQUIRE(vectorL2Collection != nullptr); + + REQUIRE(ndCollection != nullptr); + + CHECK(dynamic_cast(densityCollection.get()) != nullptr); + + CHECK(dynamic_cast(potentialCollection.get()) != nullptr); + + CHECK(dynamic_cast(fluxCollection.get()) != nullptr); + + CHECK(dynamic_cast(displacementCollection.get()) != nullptr); + + CHECK(dynamic_cast(enthalpyCollection.get()) != nullptr); + + CHECK(dynamic_cast(vectorL2Collection.get()) != nullptr); + + CHECK(dynamic_cast(ndCollection.get()) != nullptr); + + CHECK_THROWS_AS((DensityField::make_fec(0)), std::invalid_argument); + + CHECK_THROWS_AS((GravityField::make_fec(-1)), std::invalid_argument); +} + +TEST_CASE( + "Field MFEM Creates Parallel Spaces With Registered Dimensions Orders And Ordering", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = mfem::Mesh::MakeCartesian2D(4, 2, mfem::Element::QUADRILATERAL, true, 4.0, 2.0); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto densityFec = field::Field::make_fec(2); + + auto potentialFec = field::Field::make_fec(2); + + auto fluxFec = field::Field::make_fec(2); + + auto displacementFec = field::Field::make_fec(2); + + auto enthalpyFec = field::Field::make_fec(2); + + auto vectorL2Fec = + field::Field::make_fec(2); + + auto ndFec = field::Field::make_fec(2); + + auto densitySpace = field::Field::make_fespace(mesh, *densityFec); + + auto potentialSpace = field::Field::make_fespace(mesh, *potentialFec); + + auto fluxSpace = field::Field::make_fespace(mesh, *fluxFec); + + auto displacementSpace = + field::Field::make_fespace(mesh, *displacementFec); + + auto enthalpySpace = field::Field::make_fespace(mesh, *enthalpyFec); + + auto vectorL2Space = + field::Field::make_fespace( + mesh, *vectorL2Fec + ); + + auto ndSpace = field::Field::make_fespace( + mesh, *ndFec + ); + + REQUIRE(densitySpace != nullptr); + REQUIRE(potentialSpace != nullptr); + REQUIRE(fluxSpace != nullptr); + REQUIRE(displacementSpace != nullptr); + REQUIRE(enthalpySpace != nullptr); + REQUIRE(vectorL2Space != nullptr); + REQUIRE(ndSpace != nullptr); + + CHECK(densitySpace->GetVDim() == 1); + + CHECK(potentialSpace->GetVDim() == 1); + + CHECK(fluxSpace->GetVDim() == 1); + + CHECK(enthalpySpace->GetVDim() == 1); + + CHECK(displacementSpace->GetVDim() == mesh.SpaceDimension()); + + CHECK(vectorL2Space->GetVDim() == mesh.SpaceDimension()); + + CHECK(ndSpace->GetVDim() == 1); + + CHECK(densitySpace->GetOrdering() == mfem::Ordering::byNODES); + + CHECK(potentialSpace->GetOrdering() == mfem::Ordering::byNODES); + + CHECK(fluxSpace->GetOrdering() == mfem::Ordering::byNODES); + + CHECK(enthalpySpace->GetOrdering() == mfem::Ordering::byNODES); + + /* + * Displacement deliberately overrides the generic + * vector-H1 rule and is part of the project's block/indexing + * contract. + */ + CHECK(displacementSpace->GetOrdering() == mfem::Ordering::byNODES); + + /* + * A generic vector L2 quantity retains the ordinary backend + * realization, demonstrating that the displacement behavior is + * an intentional specialization rather than a global accident. + */ + CHECK(vectorL2Space->GetOrdering() == mfem::Ordering::byVDIM); + + CHECK(ndSpace->GetOrdering() == mfem::Ordering::byNODES); + + CHECK(densitySpace->GetMaxElementOrder() == field::Density::Scalar::familyOrder); + + CHECK(potentialSpace->GetMaxElementOrder() == field::Gravity::Potential::familyOrder); + + CHECK(fluxSpace->GetMaxElementOrder() == field::Gravity::Flux::familyOrder + 1); + + CHECK(displacementSpace->GetMaxElementOrder() == field::Displacement::Vector::familyOrder); + + CHECK(enthalpySpace->GetMaxElementOrder() == field::Enthalpy::Scalar::familyOrder); +} + +TEST_CASE( + "Field MFEM Typed Queries Preserve Backend Polynomial Order Semantics", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace quadrature = mean_field::quadrature; + namespace utils = mean_field::utils; + + using DensityField = field::Field; + + using GravityField = field::Field; + + using EnthalpyField = field::Field; + + const quadrature::Query densitySource = DensityField::make_query( + quadrature::QuadratureRole::projection, 3, std::array{4}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general + ); + + REQUIRE(densitySource.base_order.has_value()); + + /* + * L2_2 value order 2 + * + geometry order 3 + * + dynamic coefficient order 4. + */ + CHECK(*densitySource.base_order == 9); + + CHECK(densitySource.term == quadrature::Term::density_projection); + + CHECK(densitySource.role == quadrature::QuadratureRole::projection); + + CHECK(densitySource.domain == utils::DOMAINS::STELLAR); + + CHECK(densitySource.mapping == quadrature::MappingKind::general); + + CHECK(densitySource.geometry_weight_order == 3); + + const quadrature::Query hdivMass = + GravityField::make_query(quadrature::QuadratureRole::discretization, 2); + + REQUIRE(hdivMass.base_order.has_value()); + + /* + * RT_2 value order is 3, hence + * 3 + 3 + geometry 2 = 8. + */ + CHECK(*hdivMass.base_order == 8); + + const quadrature::Query divergence = GravityField::make_query( + quadrature::QuadratureRole::discretization, 2 + ); + + REQUIRE(divergence.base_order.has_value()); + + /* + * div(RT_2) order 2 + * + L2_2 order 2 + * + geometry 2. + */ + CHECK(*divergence.base_order == 6); + + const quadrature::Query pressureForce = EnthalpyField::make_query( + quadrature::QuadratureRole::discretization, 2, std::array{9}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general + ); + + REQUIRE(pressureForce.base_order.has_value()); + + /* + * h value order 3 + * + grad(d) order 2 + * + geometry 2 + * + n=3 pressure extra order 9 + * = 16. + */ + CHECK(*pressureForce.base_order == 16); + + const quadrature::Query equilibriumConstant = EnthalpyField::make_query( + quadrature::QuadratureRole::discretization, 2 + ); + + REQUIRE(equilibriumConstant.base_order.has_value()); + + /* + * Global scalar C contributes zero polynomial order, + * h contributes 3, and geometry contributes 2. + */ + CHECK(*equilibriumConstant.base_order == 5); + + CHECK_THROWS_AS( + (DensityField::make_query(quadrature::QuadratureRole::projection, -1)), + std::invalid_argument + ); + + const std::array negativeDynamicOrder{-1}; + + CHECK_THROWS_AS( + (EnthalpyField::make_query( + quadrature::QuadratureRole::discretization, 2, negativeDynamicOrder + )), + std::invalid_argument + ); +} + +TEST_CASE( + "Field MFEM Element Support Resolves Semantic Domains Through The Schema", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + const mfem::Mesh mesh = field_mfem_test_utils::make_two_domain_mesh(); + + CHECK((field::element_is_in_field_support(mesh, 0))); + + CHECK_FALSE((field::element_is_in_field_support(mesh, 1))); + + CHECK((field::element_is_in_field_support(mesh, 0))); + + CHECK_FALSE((field::element_is_in_field_support(mesh, 1))); + + CHECK((field::element_is_in_field_support(mesh, 0))); + + CHECK((field::element_is_in_field_support(mesh, 1))); + + CHECK((field::element_is_in_field_support(mesh, 0))); + + CHECK((field::element_is_in_field_support(mesh, 1))); +} + +TEST_CASE( + "Field MFEM L2 Stellar Support Selects Exactly Stellar Element DOFs", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh mesh = field_mfem_test_utils::make_two_domain_mesh(); + + auto fec = field::Field::make_fec(2); + + mfem::FiniteElementSpace space(&mesh, fec.get()); + + const auto support = field::resolve_field_local_dof_support(space); + + const std::vector stellarVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 0); + + const std::vector vacuumVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 1); + + REQUIRE_FALSE(stellarVDofs.empty()); + + REQUIRE_FALSE(vacuumVDofs.empty()); + + CHECK(support.activeVDofMarker.Size() == space.GetVSize()); + + CHECK(support.activeVDofs.Size() + support.inactiveVDofs.Size() == space.GetVSize()); + + for (const int vdof : stellarVDofs) { + CAPTURE(vdof); + + CHECK(support.activeVDofMarker[vdof] == 1); + + CHECK(field_mfem_test_utils::contains(support.activeVDofs, vdof)); + + CHECK_FALSE(field_mfem_test_utils::contains(support.inactiveVDofs, vdof)); + } + + for (const int vdof : vacuumVDofs) { + CAPTURE(vdof); + + CHECK(support.activeVDofMarker[vdof] == 0); + + CHECK_FALSE(field_mfem_test_utils::contains(support.activeVDofs, vdof)); + + CHECK(field_mfem_test_utils::contains(support.inactiveVDofs, vdof)); + } + + CHECK(support.activeVDofs.Size() == static_cast(stellarVDofs.size())); + + CHECK(support.inactiveVDofs.Size() == static_cast(vacuumVDofs.size())); +} + +TEST_CASE( + "Field MFEM H1 Stellar Support Keeps Shared Stellar Vacuum Trace DOFs Active", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh mesh = field_mfem_test_utils::make_two_domain_mesh(); + + auto fec = field::Field::make_fec(2); + + mfem::FiniteElementSpace space(&mesh, fec.get()); + + const auto support = field::resolve_field_local_dof_support(space); + + const std::vector stellarVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 0); + + const std::vector vacuumVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 1); + + const std::vector interfaceVDofs = field_mfem_test_utils::intersection(stellarVDofs, vacuumVDofs); + + const std::vector vacuumOnlyVDofs = field_mfem_test_utils::difference(vacuumVDofs, stellarVDofs); + + REQUIRE_FALSE(interfaceVDofs.empty()); + + REQUIRE_FALSE(vacuumOnlyVDofs.empty()); + + for (const int vdof : stellarVDofs) { + CAPTURE(vdof); + + CHECK(support.activeVDofMarker[vdof] == 1); + } + + /* + * This is the central support invariant: + * + * shared interface DOFs are active because they are touched + * by a supported stellar element, even though they are also + * touched by a vacuum element. + */ + for (const int vdof : interfaceVDofs) { + CAPTURE(vdof); + + CHECK(support.activeVDofMarker[vdof] == 1); + + CHECK(field_mfem_test_utils::contains(support.activeVDofs, vdof)); + } + + for (const int vdof : vacuumOnlyVDofs) { + CAPTURE(vdof); + + CHECK(support.activeVDofMarker[vdof] == 0); + + CHECK(field_mfem_test_utils::contains(support.inactiveVDofs, vdof)); + } + + CHECK(support.activeVDofs.Size() + support.inactiveVDofs.Size() == space.GetVSize()); +} + +TEST_CASE( + "Field MFEM All Domain Support Activates Every L2 And RT DOF", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh mesh = field_mfem_test_utils::make_two_domain_mesh(); + + auto potentialFec = field::Field::make_fec(2); + + mfem::FiniteElementSpace potentialSpace(&mesh, potentialFec.get()); + + const auto potentialSupport = + field::resolve_field_local_dof_support(potentialSpace); + + CHECK(potentialSupport.activeVDofs.Size() == potentialSpace.GetVSize()); + + CHECK(potentialSupport.inactiveVDofs.Size() == 0); + + for (int vdof = 0; vdof < potentialSupport.activeVDofMarker.Size(); ++vdof) { + CHECK(potentialSupport.activeVDofMarker[vdof] == 1); + } + + /* + * Exercise signed/oriented MFEM element VDofs through RT as + * well. The support resolver must DecodeDof() correctly. + */ + auto fluxFec = field::Field::make_fec(2); + + mfem::FiniteElementSpace fluxSpace(&mesh, fluxFec.get()); + + const auto fluxSupport = + field::resolve_field_local_dof_support(fluxSpace); + + CHECK(fluxSupport.activeVDofs.Size() == fluxSpace.GetVSize()); + + CHECK(fluxSupport.inactiveVDofs.Size() == 0); + + for (int vdof = 0; vdof < fluxSupport.activeVDofMarker.Size(); ++vdof) { + CHECK(fluxSupport.activeVDofMarker[vdof] == 1); + } +} + +TEST_CASE( + "Field MFEM Support Resolution Uses Schema Material Bindings Rather Than Hard Coded IDs", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh mesh = field_mfem_test_utils::make_two_domain_mesh(17, 29); + + auto fec = field::Field::make_fec(2); + + mfem::FiniteElementSpace space(&mesh, fec.get()); + + const auto support = + field::resolve_field_local_dof_support(space); + + const std::vector stellarVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 0); + + const std::vector vacuumVDofs = field_mfem_test_utils::decoded_element_vdofs(space, 1); + + for (const int vdof : stellarVDofs) { + CHECK(support.activeVDofMarker[vdof] == 1); + } + + for (const int vdof : vacuumVDofs) { + CHECK(support.activeVDofMarker[vdof] == 0); + } +} + +TEST_CASE( + "Field MFEM Parallel Stellar Support Produces Consistent Local And True DOF Partitions", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_mfem_test_utils::make_parallel_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto space = field::Field::make_fespace(mesh, *fec); + + REQUIRE(space != nullptr); + + const auto support = field::resolve_field_dof_support(*space); + + CHECK(support.activeVDofMarker.Size() == space->GetVSize()); + + CHECK(support.activeVDofs.Size() + support.inactiveVDofs.Size() == space->GetVSize()); + + CHECK(support.activeTrueDofMarker.Size() == space->GetTrueVSize()); + + CHECK(support.activeTrueDofs.Size() + support.inactiveTrueDofs.Size() == space->GetTrueVSize()); + + /* + * Every local DOF touched by a supported element must be active + * after shared-DOF synchronization. + */ + for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) { + const int materialId = mesh.GetAttribute(elementId); + + const bool stellar = + field_mfem_test_utils::Schema::template attribute_belongs_to( + materialId + ); + + if (!stellar) { + continue; + } + + const std::vector vdofs = field_mfem_test_utils::decoded_element_vdofs(*space, elementId); + + for (const int vdof : vdofs) { + CAPTURE(elementId, vdof); + + CHECK(support.activeVDofMarker[vdof] == 1); + } + } + + const long long globalActiveTrueDofs = field_mfem_test_utils::global_sum(support.activeTrueDofs.Size()); + + const long long globalInactiveTrueDofs = field_mfem_test_utils::global_sum(support.inactiveTrueDofs.Size()); + + /* + * The split mesh contains a finite stellar region and a finite + * vacuum region with order-three H1 structure, so both categories + * must genuinely exist globally. + */ + CHECK(globalActiveTrueDofs > 0); + + CHECK(globalInactiveTrueDofs > 0); +} + +TEST_CASE( + "Field MFEM Parallel All Support Activates Every True Displacement DOF", + tags::integration &tags::field +) { + namespace field = mean_field::field; + + mfem::Mesh serialMesh = field_mfem_test_utils::make_parallel_split_mesh(); + + mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh); + + auto fec = field::Field::make_fec(2); + + auto space = field::Field::make_fespace(mesh, *fec); + + REQUIRE(space != nullptr); + + const auto support = field::resolve_field_dof_support(*space); + + CHECK(support.inactiveVDofs.Size() == 0); + + CHECK(support.activeVDofs.Size() == space->GetVSize()); + + CHECK(support.inactiveTrueDofs.Size() == 0); + + CHECK(support.activeTrueDofs.Size() == space->GetTrueVSize()); + + for (int index = 0; index < support.activeTrueDofMarker.Size(); ++index) { + CHECK(support.activeTrueDofMarker[index] == 1); + } +} \ No newline at end of file diff --git a/tests/field/field_registry.cpp b/tests/field/field_registry.cpp new file mode 100644 index 0000000..7726081 --- /dev/null +++ b/tests/field/field_registry.cpp @@ -0,0 +1,427 @@ +#include + +#include +#include +#include +#include + +import mean_field; +import test_helpers; + +namespace field_registry_test_utils { + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + namespace quadrature = mean_field::quadrature; + + template struct TypeListSize; + + template + struct TypeListSize> : std::integral_constant { }; + + template inline constexpr std::size_t typeListSize = TypeListSize::value; + + struct MissingSupportField { + static constexpr std::string_view name = "missing_support"; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList<>; + }; + + struct InvalidSupportField { + static constexpr std::string_view name = "invalid_support"; + + struct InvalidSupport { }; + + using Support = InvalidSupport; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList<>; + }; + + struct InvalidQuantityListField { + static constexpr std::string_view name = "invalid_quantity_list"; + + using Support = field::NonSpatialSupport; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList<>; + }; + + struct InvalidFormListField { + static constexpr std::string_view name = "invalid_form_list"; + + using Support = field::NonSpatialSupport; + + using Quantities = field::TypeList; + + using Constraints = field::TypeList<>; + + using FormList = field::TypeList; + }; +} // namespace field_registry_test_utils + +TEST_CASE( + "Field Registry Recognizes Every Production Field And Rejects Incomplete Definitions", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + STATIC_REQUIRE(field::FieldTag); + + STATIC_REQUIRE(field::FieldTag); + + STATIC_REQUIRE(field::FieldTag); + + STATIC_REQUIRE(field::FieldTag); + + STATIC_REQUIRE(field::FieldTag); + + STATIC_REQUIRE_FALSE(field::FieldTag); + + STATIC_REQUIRE_FALSE(field::FieldTag); + + STATIC_REQUIRE_FALSE(field::FieldTag); + + STATIC_REQUIRE_FALSE(field::FieldTag); + + CHECK(true); +} + +TEST_CASE( + "Field Registry Assigns The Intended Semantic Support To Every Production Field", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE(field::DomainSupportedField); + + STATIC_REQUIRE(field::NonSpatialField); + + STATIC_REQUIRE(std::same_as, domain::Stellar>); + + STATIC_REQUIRE(std::same_as, domain::Stellar>); + + STATIC_REQUIRE(std::same_as, domain::All>); + + STATIC_REQUIRE(std::same_as, domain::All>); + + STATIC_REQUIRE(std::same_as, field::NonSpatialSupport>); + + CHECK(true); +} + +TEST_CASE( + "Density Registry Definition Is Complete And Self Consistent", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace quadrature = mean_field::quadrature; + + CHECK(field::Density::name == std::string_view{"density"}); + + CHECK(field::Density::Scalar::symbol == std::string_view{"ρ"}); + + STATIC_REQUIRE(field::Density::scalarOrder == 2); + + STATIC_REQUIRE(field::Density::Scalar::rankValue == 0); + + STATIC_REQUIRE(field::Density::Scalar::familyOrder == field::Density::scalarOrder); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 1); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 0); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 8); + + STATIC_REQUIRE(field::Density::constraintsAreValid); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::Density::Form::ProjectionMass::policyKey == quadrature::Term::density_projection); + + STATIC_REQUIRE(field::Density::Form::ProjectionMass::dynamicOrderCount == 0); + + STATIC_REQUIRE( + std::same_as< + typename field::Density::Form::ProjectionMass::Operands, + field::TypeList, field::Operand>> + ); + + STATIC_REQUIRE(field::Density::Form::ProjectionSource::dynamicOrderCount == 1); + + STATIC_REQUIRE(field::Density::Form::EosClosureMass::policyKey == quadrature::Term::eos_closure); + + STATIC_REQUIRE(field::Density::Form::MassConservation::policyKey == quadrature::Term::mass_conservation); + + STATIC_REQUIRE(field::Density::Form::MassNormalization::policyKey == quadrature::Term::mass_normalization); + + STATIC_REQUIRE(field::Density::Form::CenterOfMass::dynamicOrderCount == 1); + + STATIC_REQUIRE(field::Density::Form::Quadrupole::dynamicOrderCount == 1); + + CHECK(true); +} + +TEST_CASE( + "Gravity Registry Defines A Stable Mixed RT L2 Pair And All Registered Forms", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace quadrature = mean_field::quadrature; + + CHECK(field::Gravity::name == std::string_view{"gravity"}); + + CHECK(field::Gravity::Potential::symbol == std::string_view{"φ"}); + + CHECK(field::Gravity::Flux::symbol == std::string_view{"∇φ"}); + + STATIC_REQUIRE(field::Gravity::potentialOrder == 2); + + STATIC_REQUIRE(field::Gravity::fluxOrder == 2); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(field::Gravity::Potential::rankValue == 0); + + STATIC_REQUIRE(field::Gravity::Flux::rankValue == 1); + + STATIC_REQUIRE(field::DerivedQuantity); + + STATIC_REQUIRE(std::same_as, field::Gravity::Potential>); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 2); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 1); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 7); + + STATIC_REQUIRE(field::Gravity::constraintsAreValid); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::Gravity::Form::HDivMass::policyKey == quadrature::Term::gravity_hdiv_mass); + + STATIC_REQUIRE( + std::same_as< + typename field::Gravity::Form::HDivMass::Operands, + field::TypeList, field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Gravity::Form::DivergenceCoupling::Operands, + field::TypeList< + field::Operand, + field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Gravity::Form::Boundary::Operands, + field::TypeList< + field::Operand, + field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Gravity::Form::SourceLinear::Operands, + field::TypeList, field::Operand>> + ); + + CHECK(true); +} + +TEST_CASE( + "Displacement Registry Preserves Vector H1 Geometry And Force Forms", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace quadrature = mean_field::quadrature; + + CHECK(field::Displacement::name == std::string_view{"displacement"}); + + CHECK(field::Displacement::Vector::symbol == std::string_view{"d"}); + + STATIC_REQUIRE(field::Displacement::vectorOrder == 3); + + STATIC_REQUIRE(field::Displacement::Vector::rankValue == 1); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 4); + + STATIC_REQUIRE(field::Displacement::constraintsAreValid); + + STATIC_REQUIRE(field::Displacement::Form::MeshExtension::policyKey == quadrature::Term::mesh_extension); + + STATIC_REQUIRE( + std::same_as< + typename field::Displacement::Form::MeshExtension::Operands, + field::TypeList< + field::Operand, + field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Displacement::Form::GravityForce::Operands, + field::TypeList< + field::Operand, field::Operand, + field::Operand, + field::Operand>> + ); + + STATIC_REQUIRE(field::Displacement::Form::CentrifugalForce::dynamicOrderCount == 1); + + STATIC_REQUIRE(field::Displacement::Form::CentrifugalForce::policyKey == quadrature::Term::centrifugal); + + CHECK(true); +} + +TEST_CASE( + "Enthalpy Registry Preserves Continuous Stellar Field And Coupled Forms", + tags::unit &tags::field +) { + namespace field = mean_field::field; + namespace quadrature = mean_field::quadrature; + + CHECK(field::Enthalpy::name == std::string_view{"specific_enthalpy"}); + + CHECK(field::Enthalpy::Scalar::symbol == std::string_view{"h"}); + + STATIC_REQUIRE(field::Enthalpy::scalarOrder == 3); + + STATIC_REQUIRE(field::Enthalpy::Scalar::rankValue == 0); + + STATIC_REQUIRE(std::same_as); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 9); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::typeListContains); + + STATIC_REQUIRE(field::Enthalpy::Form::EosClosureSource::dynamicOrderCount == 1); + + STATIC_REQUIRE( + std::same_as< + typename field::Enthalpy::Form::EosClosureSource::Operands, + field::TypeList, field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Enthalpy::Form::EquilibriumGravity::Operands, + field::TypeList, field::Operand>> + ); + + STATIC_REQUIRE( + std::same_as< + typename field::Enthalpy::Form::EquilibriumConstant::Operands, + field::TypeList, field::Operand>> + ); + + STATIC_REQUIRE(field::Enthalpy::Form::IsobaricSurface::policyKey == quadrature::Term::isobaric_surface); + + STATIC_REQUIRE(field::Enthalpy::Form::PressureIntegral::policyKey == quadrature::Term::pressure_integral); + + STATIC_REQUIRE(field::Enthalpy::Form::PressureForce::policyKey == quadrature::Term::pressure_force); + + STATIC_REQUIRE( + std::same_as< + typename field::Enthalpy::Form::PressureForce::Operands, + field::TypeList< + field::Operand, + field::Operand>> + ); + + CHECK(true); +} + +TEST_CASE( + "Barotropic Constant Registry Is A Non Spatial Unit Sized Scalar", + tags::unit &tags::field +) { + namespace field = mean_field::field; + + CHECK(field::BarotropicConstant::name == std::string_view{"barotropic_constant"}); + + CHECK(field::BarotropicConstant::Scalar::symbol == std::string_view{"C"}); + + STATIC_REQUIRE(field::GlobalScalarQuantity); + + STATIC_REQUIRE(field::BarotropicConstant::Scalar::staticBlockSize == 1); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 1); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 0); + + STATIC_REQUIRE(field_registry_test_utils::typeListSize == 0); + + STATIC_REQUIRE(field::BarotropicConstant::constraintsAreValid); + + CHECK(true); +} \ No newline at end of file diff --git a/tests/integrators/centrifugal.cpp b/tests/integrators/centrifugal.cpp index 397ecbe..3af3ccd 100644 --- a/tests/integrators/centrifugal.cpp +++ b/tests/integrators/centrifugal.cpp @@ -13,16 +13,13 @@ namespace { const double rotation_fraction, const double sine_theta_squared ) { - const double eta = (8.0 / 27.0) * rotation_fraction * rotation_fraction; + const double eta = (8.0 / 27.0) * rotation_fraction * rotation_fraction; double surface_scale = 1.0; for (int iteration = 0; iteration < 20; ++iteration) { const double residual = - 1.0 / surface_scale + - 0.5 * eta * surface_scale * surface_scale * sine_theta_squared - - 1.0; - const double derivative = -1.0 / (surface_scale * surface_scale) + - eta * surface_scale * sine_theta_squared; + 1.0 / surface_scale + 0.5 * eta * surface_scale * surface_scale * sine_theta_squared - 1.0; + const double derivative = -1.0 / (surface_scale * surface_scale) + eta * surface_scale * sine_theta_squared; surface_scale -= residual / derivative; } @@ -39,9 +36,7 @@ TEST_CASE( constexpr double omega_value = 2.3; constexpr double tolerance = 1.0e-12; - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0 - ); + 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); @@ -49,9 +44,7 @@ TEST_CASE( mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); - mfem::FiniteElementSpace displacement_fes( - &mesh, &displacement_fec, dim, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; @@ -67,24 +60,19 @@ TEST_CASE( const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(0); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = - !domain_mapper.HasDisplacementField() - ? quadrature::MappingKind::none - : quadrature::MappingKind::general; + !domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( - integrator, quadrature::QuadratureRole::discretization, - *density_element, *velocity_element, *transformation, position_order, - utils::DOMAINS::STELLAR, mapping_kind + integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, + position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_dofs_count = velocity_element->GetDof(); @@ -110,12 +98,9 @@ TEST_CASE( element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; - integrator.AssembleElementVector( - elements, *transformation, element_state, element_residual - ); + integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); - auto residual_action = [&](const int component, - const int coordinate_weight) { + auto residual_action = [&](const int component, const int coordinate_weight) { mfem::Vector test_dofs(dim * velocity_dofs_count); mfem::Vector x_physical(dim); test_dofs = 0.0; @@ -133,25 +118,11 @@ TEST_CASE( constexpr double force_scale = density * omega_value * omega_value; - CHECK_THAT( - residual_action(0, -1), - Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance) - ); - CHECK_THAT( - residual_action(1, -1), - Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance) - ); - CHECK_THAT( - residual_action(2, -1), Catch::Matchers::WithinAbs(0.0, tolerance) - ); - CHECK_THAT( - residual_action(0, 0), - Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance) - ); - CHECK_THAT( - residual_action(1, 1), - Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance) - ); + CHECK_THAT(residual_action(0, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance)); + CHECK_THAT(residual_action(1, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance)); + CHECK_THAT(residual_action(2, -1), Catch::Matchers::WithinAbs(0.0, tolerance)); + CHECK_THAT(residual_action(0, 0), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance)); + CHECK_THAT(residual_action(1, 1), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance)); } TEST_CASE( @@ -163,9 +134,7 @@ TEST_CASE( constexpr double finite_difference_tolerance = 1.0e-9; constexpr double exact_tolerance = 1.0e-12; - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0 - ); + 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); @@ -173,9 +142,7 @@ TEST_CASE( mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); - mfem::FiniteElementSpace displacement_fes( - &mesh, &displacement_fec, dim, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; @@ -192,24 +159,19 @@ TEST_CASE( const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(0); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = - !domain_mapper.HasDisplacementField() - ? quadrature::MappingKind::none - : quadrature::MappingKind::general; + !domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( - integrator, quadrature::QuadratureRole::discretization, - *density_element, *velocity_element, *transformation, position_order, - utils::DOMAINS::STELLAR, mapping_kind + integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, + position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_size = dim * velocity_element->GetDof(); @@ -221,23 +183,20 @@ TEST_CASE( mfem::Vector density_direction(density_size); for (int i = 0; i < velocity_size; ++i) { - velocity_dofs(i) = 0.03 * static_cast(i + 1); - velocity_direction(i) = - (i % 2 == 0 ? 0.04 : -0.02) * static_cast(i + 1); + velocity_dofs(i) = 0.03 * static_cast(i + 1); + velocity_direction(i) = (i % 2 == 0 ? 0.04 : -0.02) * static_cast(i + 1); } for (int i = 0; i < density_size; ++i) { - density_dofs(i) = 1.0 + 0.08 * static_cast(i + 1); - density_direction(i) = - (i % 2 == 0 ? 0.05 : -0.03) * static_cast(i + 1); + density_dofs(i) = 1.0 + 0.08 * static_cast(i + 1); + density_direction(i) = (i % 2 == 0 ? 0.05 : -0.03) * static_cast(i + 1); } mfem::Array elements(2); elements[0] = velocity_element; elements[1] = density_element; - auto assemble_velocity_residual = [&](const mfem::Vector &velocity, - const mfem::Vector &density) { + auto assemble_velocity_residual = [&](const mfem::Vector &velocity, const mfem::Vector &density) { mfem::Array element_state(2); element_state[0] = &velocity; element_state[1] = &density; @@ -249,9 +208,7 @@ TEST_CASE( element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; - integrator.AssembleElementVector( - elements, *transformation, element_state, element_residual - ); + integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return velocity_residual; }; @@ -270,9 +227,7 @@ TEST_CASE( element_jacobian(1, 0) = &drho_dv; element_jacobian(1, 1) = &drho_drho; - integrator.AssembleElementGrad( - elements, *transformation, element_state, element_jacobian - ); + integrator.AssembleElementGrad(elements, *transformation, element_state, element_jacobian); mfem::Vector velocity_plus(velocity_dofs); mfem::Vector velocity_minus(velocity_dofs); @@ -284,10 +239,8 @@ TEST_CASE( density_plus.Add(step, density_direction); density_minus.Add(-step, density_direction); - mfem::Vector residual_plus = - assemble_velocity_residual(velocity_plus, density_plus); - mfem::Vector residual_minus = - assemble_velocity_residual(velocity_minus, density_minus); + mfem::Vector residual_plus = assemble_velocity_residual(velocity_plus, density_plus); + mfem::Vector residual_minus = assemble_velocity_residual(velocity_minus, density_minus); mfem::Vector finite_difference(residual_plus); finite_difference -= residual_minus; finite_difference /= 2.0 * step; @@ -303,49 +256,33 @@ TEST_CASE( mfem::Vector finite_difference_error(jacobian_action); finite_difference_error -= finite_difference; - const double finite_difference_scale = - std::max(1.0, finite_difference.Norml2()); - const double relative_finite_difference_error = - finite_difference_error.Norml2() / finite_difference_scale; + const double finite_difference_scale = std::max(1.0, finite_difference.Norml2()); + const double relative_finite_difference_error = finite_difference_error.Norml2() / finite_difference_scale; - CHECK_THAT( - relative_finite_difference_error, - Catch::Matchers::WithinAbs(0.0, finite_difference_tolerance) - ); - CHECK_THAT( - velocity_block_action.Norml2(), - Catch::Matchers::WithinAbs(0.0, exact_tolerance) - ); + CHECK_THAT(relative_finite_difference_error, Catch::Matchers::WithinAbs(0.0, finite_difference_tolerance)); + CHECK_THAT(velocity_block_action.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance)); - mfem::Vector density_direction_residual = - assemble_velocity_residual(velocity_dofs, density_direction); + mfem::Vector density_direction_residual = assemble_velocity_residual(velocity_dofs, density_direction); mfem::Vector density_linearity_error(density_block_action); density_linearity_error -= density_direction_residual; - CHECK_THAT( - density_linearity_error.Norml2(), - Catch::Matchers::WithinAbs(0.0, exact_tolerance) - ); + CHECK_THAT(density_linearity_error.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance)); double inactive_block_maximum = 0.0; for (int i = 0; i < drho_dv.Height(); ++i) { for (int j = 0; j < drho_dv.Width(); ++j) { - inactive_block_maximum = - std::max(inactive_block_maximum, std::abs(drho_dv(i, j))); + inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_dv(i, j))); } } for (int i = 0; i < drho_drho.Height(); ++i) { for (int j = 0; j < drho_drho.Width(); ++j) { - inactive_block_maximum = - std::max(inactive_block_maximum, std::abs(drho_drho(i, j))); + inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_drho(i, j))); } } - CHECK_THAT( - inactive_block_maximum, Catch::Matchers::WithinAbs(0.0, exact_tolerance) - ); + CHECK_THAT(inactive_block_maximum, Catch::Matchers::WithinAbs(0.0, exact_tolerance)); } TEST_CASE( @@ -357,9 +294,7 @@ TEST_CASE( constexpr double omega_scale = 2.3; constexpr double tolerance = 1.0e-12; - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0 - ); + 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); @@ -367,9 +302,7 @@ TEST_CASE( mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); - mfem::FiniteElementSpace displacement_fes( - &mesh, &displacement_fec, dim, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; @@ -386,24 +319,19 @@ TEST_CASE( const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(0); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = - !domain_mapper.HasDisplacementField() - ? quadrature::MappingKind::none - : quadrature::MappingKind::general; + !domain_mapper.HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( - integrator, quadrature::QuadratureRole::discretization, - *density_element, *velocity_element, *transformation, position_order, - utils::DOMAINS::STELLAR, mapping_kind + integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, + position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_dofs_count = velocity_element->GetDof(); @@ -433,9 +361,7 @@ TEST_CASE( element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; - integrator.AssembleElementVector( - elements, *transformation, element_state, element_residual - ); + integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return velocity_residual; }; @@ -445,9 +371,7 @@ TEST_CASE( zero_omega = 0.0; const mfem::Vector zero_residual = assemble_velocity_residual(zero_omega); - CHECK_THAT( - zero_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance) - ); + CHECK_THAT(zero_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::Vector negative_omega(omega); negative_omega *= -1.0; @@ -465,12 +389,8 @@ TEST_CASE( mfem::Vector scaling_error = assemble_velocity_residual(scaled_omega); scaling_error -= expected_scaled_residual; - const double scaling_error_relative = - scaling_error.Norml2() / - std::max(1.0, expected_scaled_residual.Norml2()); - CHECK_THAT( - scaling_error_relative, Catch::Matchers::WithinAbs(0.0, tolerance) - ); + const double scaling_error_relative = scaling_error.Norml2() / std::max(1.0, expected_scaled_residual.Norml2()); + CHECK_THAT(scaling_error_relative, Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::Vector axis_test_dofs(velocity_size); mfem::Vector torque_test_dofs(velocity_size); @@ -485,17 +405,13 @@ TEST_CASE( for (int i = 0; i < velocity_dofs_count; ++i) { transformation->Transform(nodes.IntPoint(i), x_physical); - azimuthal_direction(0) = - omega(1) * x_physical(2) - omega(2) * x_physical(1); - azimuthal_direction(1) = - omega(2) * x_physical(0) - omega(0) * x_physical(2); - azimuthal_direction(2) = - omega(0) * x_physical(1) - omega(1) * x_physical(0); + azimuthal_direction(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); + azimuthal_direction(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); + azimuthal_direction(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); for (int c = 0; c < dim; ++c) { - axis_test_dofs(i + c * velocity_dofs_count) = omega(c); - torque_test_dofs(i + c * velocity_dofs_count) = - azimuthal_direction(c); + axis_test_dofs(i + c * velocity_dofs_count) = omega(c); + torque_test_dofs(i + c * velocity_dofs_count) = azimuthal_direction(c); } } @@ -510,11 +426,11 @@ TEST_CASE( "Centrifugal Integrator Matches Rotational Virial On Roche Mappings", tags::integration &tags::solver &tags::integrator &tags::centrifugal ) { - auto args = test_utils::setup_args(); - fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - constexpr int dim = 3; - constexpr double concentration = 4.0; + constexpr int dim = 3; + constexpr double concentration = 4.0; constexpr double assembly_tolerance = 1.0e-7; constexpr double position_tolerance = 1.0e-6; @@ -527,8 +443,7 @@ TEST_CASE( return 0.0; } - const double denominator = - 1.0 + concentration * normalized_radius_squared; + const double denominator = 1.0 + concentration * normalized_radius_squared; return (1.0 - normalized_radius_squared) / (denominator * denominator); }; @@ -538,23 +453,17 @@ TEST_CASE( mfem::ParGridFunction displacement(f.displacementFes.get()); - const mfem::FiniteElement &representative_velocity_element = - *f.displacementFes->GetTypicalFE(); - const mfem::FiniteElement &representative_density_element = - *f.densityFes->GetTypicalFE(); - mfem::ElementTransformation &representative_transformation = - *f.mesh->GetElementTransformation(0); - const int position_order = f.displacementFes->GetMaxElementOrder(); + const mfem::FiniteElement &representative_velocity_element = *f.displacementFes->GetTypicalFE(); + const mfem::FiniteElement &representative_density_element = *f.densityFes->GetTypicalFE(); + mfem::ElementTransformation &representative_transformation = *f.mesh->GetElementTransformation(0); + const int position_order = f.displacementFes->GetMaxElementOrder(); - for (constexpr std::array rotation_fractions = - {0.0001, 0.1, 0.25, 0.50, 0.70, 0.85, 0.95, 0.99}; + for (constexpr std::array rotation_fractions = {0.0001, 0.1, 0.25, 0.50, 0.70, 0.85, 0.95, 0.99}; const double rotation_fraction : rotation_fractions) { CAPTURE(rotation_fraction); - auto rotation_displacement = [radius, rotation_fraction]( - const mfem::Vector &x, - mfem::Vector &displacement_value - ) { + auto rotation_displacement = [radius, + rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; @@ -565,24 +474,17 @@ TEST_CASE( } const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); - const double sine_theta_squared = - cylindrical_radius_squared / radius_squared; - const double surface_scale = compute_roche_surface_scale( - rotation_fraction, sine_theta_squared - ); - const double radial_weight = - std::min(radius_squared / (radius * radius), 1.0); - const double mapped_scale = - 1.0 + radial_weight * (surface_scale - 1.0); + const double sine_theta_squared = cylindrical_radius_squared / radius_squared; + const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); + const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); + const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dim, rotation_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); f.mapping->SetDisplacement(displacement); @@ -593,46 +495,34 @@ TEST_CASE( integrators::CentrifugalForceIntegrator integrator(*f.mapping, omega); const quadrature::MappingKind mapping_kind = - !f.mapping->HasDisplacementField() - ? quadrature::MappingKind::none - : quadrature::MappingKind::general; + !f.mapping->HasDisplacementField() ? quadrature::MappingKind::none : quadrature::MappingKind::general; f.quadratureFactory->configure_centrifugal( - integrator, quadrature::QuadratureRole::discretization, - representative_density_element, representative_velocity_element, - representative_transformation, position_order, - utils::DOMAINS::STELLAR, mapping_kind + integrator, quadrature::QuadratureRole::discretization, representative_density_element, + representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR, + mapping_kind ); const int reference_order = - 2 * std::max( - f.displacementFes->GetMaxElementOrder(), - f.densityFes->GetMaxElementOrder() - ) + - 16; + 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; double local_residual_action = 0.0; double local_discrete_reference_action = 0.0; double local_continuous_reference_action = 0.0; - double local_minimum_map_determinant = - std::numeric_limits::infinity(); - double local_maximum_map_determinant = - std::numeric_limits::lowest(); + double local_minimum_map_determinant = std::numeric_limits::infinity(); + double local_maximum_map_determinant = std::numeric_limits::lowest(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } - const mfem::FiniteElement *velocity_element = - f.displacementFes->GetFE(elem_id); - const mfem::FiniteElement *density_element = - f.densityFes->GetFE(elem_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); + const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); + const mfem::FiniteElement *density_element = f.densityFes->GetFE(elem_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); - const int velocity_dofs_count = velocity_element->GetDof(); - const int velocity_size = dim * velocity_dofs_count; - const int density_size = density_element->GetDof(); + const int velocity_dofs_count = velocity_element->GetDof(); + const int velocity_size = dim * velocity_dofs_count; + const int density_size = density_element->GetDof(); mfem::Array density_dof_indices; mfem::Vector density_dofs; @@ -659,16 +549,13 @@ TEST_CASE( element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; - integrator.AssembleElementVector( - elements, *transformation, element_state, element_residual - ); + integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); - position_test_dofs = 0.0; + position_test_dofs = 0.0; - const mfem::IntegrationRule &velocity_nodes = - velocity_element->GetNodes(); + 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); @@ -676,8 +563,7 @@ TEST_CASE( f.mapping->GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { - position_test_dofs(i + d * velocity_dofs_count) = - x_physical(d); + position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } @@ -688,71 +574,45 @@ TEST_CASE( mfem::Vector omega_cross_position(dim); mfem::Vector centrifugal_acceleration(dim); - const mfem::IntegrationRule &reference_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), reference_order - ); + const mfem::IntegrationRule &reference_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - reference_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - const double signed_map_determinant = - f.mapping->ComputeDetJ(*transformation, integration_point); + const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); const mapping::VolumeQuadratureContext context = - f.mapping->GetQuadratureContext( - *transformation, integration_point - ); + f.mapping->GetQuadratureContext(*transformation, integration_point); - local_minimum_map_determinant = std::min( - local_minimum_map_determinant, signed_map_determinant - ); - local_maximum_map_determinant = std::max( - local_maximum_map_determinant, signed_map_determinant - ); + local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant); + local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant); - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { - position_test_value(d) += - position_test_dofs(i + d * velocity_dofs_count) * - velocity_shape(i); + position_test_value(d) += position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } - omega_cross_position(0) = - omega(1) * x_physical(2) - omega(2) * x_physical(1); - omega_cross_position(1) = - omega(2) * x_physical(0) - omega(0) * x_physical(2); - omega_cross_position(2) = - omega(0) * x_physical(1) - omega(1) * x_physical(0); + omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); + omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); + omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); - centrifugal_acceleration(0) = - omega(1) * omega_cross_position(2) - - omega(2) * omega_cross_position(1); - centrifugal_acceleration(1) = - omega(2) * omega_cross_position(0) - - omega(0) * omega_cross_position(2); - centrifugal_acceleration(2) = - omega(0) * omega_cross_position(1) - - omega(1) * omega_cross_position(0); + centrifugal_acceleration(0) = omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); + centrifugal_acceleration(1) = omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); + centrifugal_acceleration(2) = omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); - const double density_value = - density.GetValue(elem_id, integration_point); + const double density_value = density.GetValue(elem_id, integration_point); local_discrete_reference_action += - density_value * - (position_test_value * centrifugal_acceleration) * - context.weight; + density_value * (position_test_value * centrifugal_acceleration) * context.weight; local_continuous_reference_action += - density_value * (x_physical * centrifugal_acceleration) * - context.weight; + density_value * (x_physical * centrifugal_acceleration) * context.weight; } } @@ -763,74 +623,42 @@ TEST_CASE( double global_maximum_map_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); + MPI_Allreduce(&local_residual_action, &global_residual_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( - &local_residual_action, &global_residual_action, 1, MPI_DOUBLE, - MPI_SUM, communicator + &local_discrete_reference_action, &global_discrete_reference_action, 1, MPI_DOUBLE, MPI_SUM, communicator ); MPI_Allreduce( - &local_discrete_reference_action, &global_discrete_reference_action, - 1, MPI_DOUBLE, MPI_SUM, communicator - ); - MPI_Allreduce( - &local_continuous_reference_action, - &global_continuous_reference_action, 1, MPI_DOUBLE, MPI_SUM, + &local_continuous_reference_action, &global_continuous_reference_action, 1, MPI_DOUBLE, MPI_SUM, communicator ); MPI_Allreduce( - &local_minimum_map_determinant, &global_minimum_map_determinant, 1, - MPI_DOUBLE, MPI_MIN, communicator + &local_minimum_map_determinant, &global_minimum_map_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); MPI_Allreduce( - &local_maximum_map_determinant, &global_maximum_map_determinant, 1, - MPI_DOUBLE, MPI_MAX, communicator + &local_maximum_map_determinant, &global_maximum_map_determinant, 1, MPI_DOUBLE, MPI_MAX, communicator ); - const double relative_assembly_error = - std::abs( - global_residual_action - global_discrete_reference_action - ) / - std::abs(global_discrete_reference_action); + const double relative_assembly_error = std::abs(global_residual_action - global_discrete_reference_action) / + std::abs(global_discrete_reference_action); const double relative_position_error = - std::abs( - global_discrete_reference_action - - global_continuous_reference_action - ) / + std::abs(global_discrete_reference_action - global_continuous_reference_action) / std::abs(global_continuous_reference_action); - const double equatorial_scale = - compute_roche_surface_scale(rotation_fraction, 1.0); + const double equatorial_scale = compute_roche_surface_scale(rotation_fraction, 1.0); INFO("Rotation fraction = " << rotation_fraction); INFO("Roche equatorial scale = " << equatorial_scale); - INFO( - "Minimum mapping determinant = " << global_minimum_map_determinant - ); - INFO( - "Maximum mapping determinant = " << global_maximum_map_determinant - ); + INFO("Minimum mapping determinant = " << global_minimum_map_determinant); + INFO("Maximum mapping determinant = " << global_maximum_map_determinant); INFO("Assembled centrifugal virial = " << global_residual_action); - INFO( - "Discrete reference virial = " << global_discrete_reference_action - ); - INFO( - "Continuous reference virial = " - << global_continuous_reference_action - ); + INFO("Discrete reference virial = " << global_discrete_reference_action); + INFO("Continuous reference virial = " << global_continuous_reference_action); INFO("Relative assembly error = " << relative_assembly_error); - INFO( - "Relative position representation error = " - << relative_position_error - ); + INFO("Relative position representation error = " << relative_position_error); REQUIRE(equatorial_scale > 1.0); REQUIRE(global_minimum_map_determinant > 0.0); - CHECK_THAT( - relative_assembly_error, - Catch::Matchers::WithinAbs(0.0, assembly_tolerance) - ); - CHECK_THAT( - relative_position_error, - Catch::Matchers::WithinAbs(0.0, position_tolerance) - ); + CHECK_THAT(relative_assembly_error, Catch::Matchers::WithinAbs(0.0, assembly_tolerance)); + CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance)); } f.mapping->ResetDisplacement(); @@ -841,22 +669,15 @@ TEST_CASE( "Registered Order", tags::integration &tags::solver &tags::integrator &tags::centrifugal ) { - constexpr int dim = 3; - constexpr double concentration = 4.0; - constexpr std::array velocity_orders = { - field::Displacement::Vector::familyOrder - }; + constexpr int dim = 3; + constexpr double concentration = 4.0; + constexpr std::array velocity_orders = {field::Displacement::Vector::familyOrder}; constexpr std::array rotation_fractions = {0.70, 0.99}; - std::array< - std::array, rotation_fractions.size()> - position_errors{}; - std::array< - std::array, rotation_fractions.size()> - minimum_determinants{}; + std::array, rotation_fractions.size()> position_errors{}; + std::array, rotation_fractions.size()> minimum_determinants{}; - for (std::size_t order_index = 0; order_index < velocity_orders.size(); - ++order_index) { + for (std::size_t order_index = 0; order_index < velocity_orders.size(); ++order_index) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); @@ -864,17 +685,14 @@ TEST_CASE( constexpr double radius = utils::RADIUS; auto reference_density = [radius](const mfem::Vector &x) { - const double normalized_radius_squared = - (x * x) / (radius * radius); + const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } - const double denominator = - 1.0 + concentration * normalized_radius_squared; - return (1.0 - normalized_radius_squared) / - (denominator * denominator); + const double denominator = 1.0 + concentration * normalized_radius_squared; + return (1.0 - normalized_radius_squared) / (denominator * denominator); }; mfem::FunctionCoefficient density_coefficient(reference_density); @@ -883,14 +701,11 @@ TEST_CASE( mfem::ParGridFunction displacement(f.displacementFes.get()); - for (std::size_t rotation_index = 0; - rotation_index < rotation_fractions.size(); ++rotation_index) { + for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double rotation_fraction = rotation_fractions[rotation_index]; - auto rotation_displacement = [radius, rotation_fraction]( - const mfem::Vector &x, - mfem::Vector &displacement_value - ) { + auto rotation_displacement = [radius, + rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; @@ -900,26 +715,18 @@ TEST_CASE( return; } - const double cylindrical_radius_squared = - x(0) * x(0) + x(1) * x(1); - const double sine_theta_squared = - cylindrical_radius_squared / radius_squared; - const double surface_scale = compute_roche_surface_scale( - rotation_fraction, sine_theta_squared - ); - const double radial_weight = - std::min(radius_squared / (radius * radius), 1.0); - const double mapped_scale = - 1.0 + radial_weight * (surface_scale - 1.0); + const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); + const double sine_theta_squared = cylindrical_radius_squared / radius_squared; + const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); + const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); + const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dim, rotation_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); f.mapping->SetDisplacement(displacement); @@ -928,48 +735,36 @@ TEST_CASE( omega(2) = rotation_fraction; const int reference_order = - 2 * std::max( - f.displacementFes->GetMaxElementOrder(), - f.densityFes->GetMaxElementOrder() - ) + - 16; + 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; - double local_discrete_action = 0.0; - double local_continuous_action = 0.0; - double local_minimum_determinant = - std::numeric_limits::infinity(); + double local_discrete_action = 0.0; + double local_continuous_action = 0.0; + double local_minimum_determinant = std::numeric_limits::infinity(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } - const mfem::FiniteElement *velocity_element = - f.displacementFes->GetFE(elem_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); + const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); - const int velocity_dofs_count = velocity_element->GetDof(); - const int velocity_size = dim * velocity_dofs_count; + const int velocity_dofs_count = velocity_element->GetDof(); + const int velocity_size = dim * velocity_dofs_count; mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); - position_test_dofs = 0.0; + position_test_dofs = 0.0; - const mfem::IntegrationRule &velocity_nodes = - velocity_element->GetNodes(); + 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); + const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); - f.mapping->GetPhysicalPoint( - *transformation, node, x_physical - ); + f.mapping->GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { - position_test_dofs(i + d * velocity_dofs_count) = - x_physical(d); + position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } @@ -979,75 +774,46 @@ TEST_CASE( mfem::Vector centrifugal_acceleration(dim); const mfem::IntegrationRule &reference_rule = - mfem::IntRules.Get( - transformation->GetGeometryType(), reference_order - ); + mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - reference_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - const double signed_map_determinant = - f.mapping->ComputeDetJ( - *transformation, integration_point - ); + const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); const mapping::VolumeQuadratureContext context = - f.mapping->GetQuadratureContext( - *transformation, integration_point - ); + f.mapping->GetQuadratureContext(*transformation, integration_point); - local_minimum_determinant = std::min( - local_minimum_determinant, signed_map_determinant - ); + local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); - velocity_element->CalcShape( - integration_point, velocity_shape - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); + velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { position_test_value(d) += - position_test_dofs( - i + d * velocity_dofs_count - ) * - velocity_shape(i); + position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } - omega_cross_position(0) = - omega(1) * x_physical(2) - omega(2) * x_physical(1); - omega_cross_position(1) = - omega(2) * x_physical(0) - omega(0) * x_physical(2); - omega_cross_position(2) = - omega(0) * x_physical(1) - omega(1) * x_physical(0); + omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); + omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); + omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); centrifugal_acceleration(0) = - omega(1) * omega_cross_position(2) - - omega(2) * omega_cross_position(1); + omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); centrifugal_acceleration(1) = - omega(2) * omega_cross_position(0) - - omega(0) * omega_cross_position(2); + omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); centrifugal_acceleration(2) = - omega(0) * omega_cross_position(1) - - omega(1) * omega_cross_position(0); + omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); - const double density_value = - density.GetValue(elem_id, integration_point); + const double density_value = density.GetValue(elem_id, integration_point); local_discrete_action += - density_value * - (position_test_value * centrifugal_acceleration) * - context.weight; - local_continuous_action += - density_value * - (x_physical * centrifugal_acceleration) * - context.weight; + density_value * (position_test_value * centrifugal_acceleration) * context.weight; + local_continuous_action += density_value * (x_physical * centrifugal_acceleration) * context.weight; } } @@ -1056,43 +822,28 @@ TEST_CASE( double global_minimum_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); + MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( - &local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, - MPI_SUM, communicator - ); - MPI_Allreduce( - &local_continuous_action, &global_continuous_action, 1, - MPI_DOUBLE, MPI_SUM, communicator - ); - MPI_Allreduce( - &local_minimum_determinant, &global_minimum_determinant, 1, - MPI_DOUBLE, MPI_MIN, communicator + &local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); position_errors[rotation_index][order_index] = - std::abs(global_discrete_action - global_continuous_action) / - std::abs(global_continuous_action); - minimum_determinants[rotation_index][order_index] = - global_minimum_determinant; + std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action); + minimum_determinants[rotation_index][order_index] = global_minimum_determinant; } f.mapping->ResetDisplacement(); } - for (std::size_t rotation_index = 0; - rotation_index < rotation_fractions.size(); ++rotation_index) { + for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { constexpr double consistency_tolerance = 1.0e-1; - const double registered_order_error = - position_errors[rotation_index][0]; + const double registered_order_error = position_errors[rotation_index][0]; CAPTURE(rotation_fractions[rotation_index]); - INFO( - "Registered displacement family order = " << velocity_orders.front() - ); + INFO("Registered displacement family order = " << velocity_orders.front()); INFO("Position error = " << registered_order_error); - INFO( - "Minimum determinant = " << minimum_determinants[rotation_index][0] - ); + INFO("Minimum determinant = " << minimum_determinants[rotation_index][0]); REQUIRE(minimum_determinants[rotation_index][0] > 0.0); REQUIRE(std::isfinite(registered_order_error)); @@ -1102,8 +853,7 @@ TEST_CASE( TEST_CASE( "Centrifugal Virial Position Representation Converges Under H Refinement", - tags::integration &tags::solver &tags::integrator &tags::convergence - &tags::h_refinement &tags::centrifugal + tags::integration &tags::solver &tags::integrator &tags::convergence &tags::h_refinement &tags::centrifugal ) { constexpr int dim = 3; constexpr double concentration = 4.0; @@ -1112,35 +862,25 @@ TEST_CASE( constexpr std::array refinement_levels = {0, 1, 2}; constexpr std::array rotation_fractions = {0.70, 0.85}; - std::array< - std::array, rotation_fractions.size()> - position_errors{}; - std::array< - std::array, rotation_fractions.size()> - minimum_determinants{}; + std::array, rotation_fractions.size()> position_errors{}; + std::array, rotation_fractions.size()> minimum_determinants{}; - for (std::size_t refinement_index = 0; - refinement_index < refinement_levels.size(); ++refinement_index) { - auto args = test_utils::setup_args(); + for (std::size_t refinement_index = 0; refinement_index < refinement_levels.size(); ++refinement_index) { + auto args = test_utils::setup_args(); - fem::FEM f = fem::setup_fem( - args.mesh_file, args, refinement_levels[refinement_index] - ); + fem::FEM f = fem::setup_fem(args.mesh_file, args, refinement_levels[refinement_index]); const double radius = utils::RADIUS; auto reference_density = [radius](const mfem::Vector &x) { - const double normalized_radius_squared = - (x * x) / (radius * radius); + const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } - const double denominator = - 1.0 + concentration * normalized_radius_squared; - return (1.0 - normalized_radius_squared) / - (denominator * denominator); + const double denominator = 1.0 + concentration * normalized_radius_squared; + return (1.0 - normalized_radius_squared) / (denominator * denominator); }; mfem::FunctionCoefficient density_coefficient(reference_density); @@ -1149,14 +889,11 @@ TEST_CASE( mfem::ParGridFunction displacement(f.displacementFes.get()); - for (std::size_t rotation_index = 0; - rotation_index < rotation_fractions.size(); ++rotation_index) { + for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double rotation_fraction = rotation_fractions[rotation_index]; - auto rotation_displacement = [radius, rotation_fraction]( - const mfem::Vector &x, - mfem::Vector &displacement_value - ) { + auto rotation_displacement = [radius, + rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; @@ -1166,26 +903,18 @@ TEST_CASE( return; } - const double cylindrical_radius_squared = - x(0) * x(0) + x(1) * x(1); - const double sine_theta_squared = - cylindrical_radius_squared / radius_squared; - const double surface_scale = compute_roche_surface_scale( - rotation_fraction, sine_theta_squared - ); - const double radial_weight = - std::min(radius_squared / (radius * radius), 1.0); - const double mapped_scale = - 1.0 + radial_weight * (surface_scale - 1.0); + const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); + const double sine_theta_squared = cylindrical_radius_squared / radius_squared; + const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); + const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); + const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dim, rotation_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); f.mapping->SetDisplacement(displacement); @@ -1194,48 +923,36 @@ TEST_CASE( omega(2) = rotation_fraction; const int reference_order = - 2 * std::max( - f.displacementFes->GetMaxElementOrder(), - f.densityFes->GetMaxElementOrder() - ) + - 16; + 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; - double local_discrete_action = 0.0; - double local_continuous_action = 0.0; - double local_minimum_determinant = - std::numeric_limits::infinity(); + double local_discrete_action = 0.0; + double local_continuous_action = 0.0; + double local_minimum_determinant = std::numeric_limits::infinity(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } - const mfem::FiniteElement *velocity_element = - f.displacementFes->GetFE(elem_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); + const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); - const int velocity_dofs_count = velocity_element->GetDof(); - const int velocity_size = dim * velocity_dofs_count; + const int velocity_dofs_count = velocity_element->GetDof(); + const int velocity_size = dim * velocity_dofs_count; mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); - position_test_dofs = 0.0; + position_test_dofs = 0.0; - const mfem::IntegrationRule &velocity_nodes = - velocity_element->GetNodes(); + 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); + const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); - f.mapping->GetPhysicalPoint( - *transformation, node, x_physical - ); + f.mapping->GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { - position_test_dofs(i + d * velocity_dofs_count) = - x_physical(d); + position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } @@ -1245,75 +962,46 @@ TEST_CASE( mfem::Vector centrifugal_acceleration(dim); const mfem::IntegrationRule &reference_rule = - mfem::IntRules.Get( - transformation->GetGeometryType(), reference_order - ); + mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - reference_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - const double signed_map_determinant = - f.mapping->ComputeDetJ( - *transformation, integration_point - ); + const double signed_map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); const mapping::VolumeQuadratureContext context = - f.mapping->GetQuadratureContext( - *transformation, integration_point - ); + f.mapping->GetQuadratureContext(*transformation, integration_point); - local_minimum_determinant = std::min( - local_minimum_determinant, signed_map_determinant - ); + local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); - velocity_element->CalcShape( - integration_point, velocity_shape - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); + velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { position_test_value(d) += - position_test_dofs( - i + d * velocity_dofs_count - ) * - velocity_shape(i); + position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } - omega_cross_position(0) = - omega(1) * x_physical(2) - omega(2) * x_physical(1); - omega_cross_position(1) = - omega(2) * x_physical(0) - omega(0) * x_physical(2); - omega_cross_position(2) = - omega(0) * x_physical(1) - omega(1) * x_physical(0); + omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); + omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); + omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); centrifugal_acceleration(0) = - omega(1) * omega_cross_position(2) - - omega(2) * omega_cross_position(1); + omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); centrifugal_acceleration(1) = - omega(2) * omega_cross_position(0) - - omega(0) * omega_cross_position(2); + omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); centrifugal_acceleration(2) = - omega(0) * omega_cross_position(1) - - omega(1) * omega_cross_position(0); + omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); - const double density_value = - density.GetValue(elem_id, integration_point); + const double density_value = density.GetValue(elem_id, integration_point); local_discrete_action += - density_value * - (position_test_value * centrifugal_acceleration) * - context.weight; - local_continuous_action += - density_value * - (x_physical * centrifugal_acceleration) * - context.weight; + density_value * (position_test_value * centrifugal_acceleration) * context.weight; + local_continuous_action += density_value * (x_physical * centrifugal_acceleration) * context.weight; } } @@ -1322,31 +1010,21 @@ TEST_CASE( double global_minimum_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); + MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( - &local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, - MPI_SUM, communicator - ); - MPI_Allreduce( - &local_continuous_action, &global_continuous_action, 1, - MPI_DOUBLE, MPI_SUM, communicator - ); - MPI_Allreduce( - &local_minimum_determinant, &global_minimum_determinant, 1, - MPI_DOUBLE, MPI_MIN, communicator + &local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); position_errors[rotation_index][refinement_index] = - std::abs(global_discrete_action - global_continuous_action) / - std::abs(global_continuous_action); - minimum_determinants[rotation_index][refinement_index] = - global_minimum_determinant; + std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action); + minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant; } f.mapping->ResetDisplacement(); } - for (std::size_t rotation_index = 0; - rotation_index < rotation_fractions.size(); ++rotation_index) { + for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double error_h = position_errors[rotation_index][0]; const double error_h2 = position_errors[rotation_index][1]; const double error_h4 = position_errors[rotation_index][2]; @@ -1366,18 +1044,9 @@ TEST_CASE( INFO("Level 1 to 2 reduction = " << error_h2 / error_h4); INFO("Observed level 0 to 1 rate = " << rate_h_h2); INFO("Observed level 1 to 2 rate = " << rate_h2_h4); - INFO( - "Level 0 minimum determinant = " - << minimum_determinants[rotation_index][0] - ); - INFO( - "Level 1 minimum determinant = " - << minimum_determinants[rotation_index][1] - ); - INFO( - "Level 2 minimum determinant = " - << minimum_determinants[rotation_index][2] - ); + INFO("Level 0 minimum determinant = " << minimum_determinants[rotation_index][0]); + INFO("Level 1 minimum determinant = " << minimum_determinants[rotation_index][1]); + INFO("Level 2 minimum determinant = " << minimum_determinants[rotation_index][2]); REQUIRE(minimum_determinants[rotation_index][0] > 0.0); REQUIRE(minimum_determinants[rotation_index][1] > 0.0); @@ -1387,8 +1056,6 @@ TEST_CASE( CHECK(error_h4 < error_h2); CHECK(rate_h_h2 > minimum_rate); CHECK(rate_h2_h4 > minimum_rate); - CHECK_THAT( - error_h4, Catch::Matchers::WithinAbs(0.0, finest_level_tolerance) - ); + CHECK_THAT(error_h4, Catch::Matchers::WithinAbs(0.0, finest_level_tolerance)); } } diff --git a/tests/integrators/gravity.cpp b/tests/integrators/gravity.cpp index e7ebada..16769b1 100644 --- a/tests/integrators/gravity.cpp +++ b/tests/integrators/gravity.cpp @@ -17,21 +17,14 @@ TEST_CASE( 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; + 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::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); @@ -39,54 +32,35 @@ TEST_CASE( mfem::L2_FECollection gravity_potential_fec(1, dim); mfem::H1_FECollection displacement_fec(2, dim); - mfem::FiniteElementSpace velocity_fes( - &mesh, &velocity_fec, dim, mfem::Ordering::byVDIM - ); + 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 gravity_potential_fes(&mesh, &gravity_potential_fec); + mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; mapping::DomainMapper domain_mapper(displacement, 1.0, 2.0); - INFO( - std::format( - "Domain mapping is has displacement field: {}", - domain_mapper.HasDisplacementField() - ) - ); - INFO( - std::format( - "Domain mapping is identity: {}", domain_mapper.CalcIsIdentity() - ) - ); + INFO(std::format("Domain mapping is has displacement field: {}", domain_mapper.HasDisplacementField())); + INFO(std::format("Domain mapping is identity: {}", domain_mapper.CalcIsIdentity())); REQUIRE(domain_mapper.CalcIsIdentity()); - 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 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; + 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); @@ -116,9 +90,8 @@ TEST_CASE( } 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)); + 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) { @@ -161,14 +134,10 @@ TEST_CASE( ); 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 + 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); @@ -180,9 +149,7 @@ TEST_CASE( dv_dgrad_phi = 1.0; dv_ddisplacement = 1.0; - mfem::Array2D element_matrices( - block_count, block_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) { @@ -193,25 +160,19 @@ TEST_CASE( 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; + element_matrices(velocity_block, displacement_block) = &dv_ddisplacement; - integrator.AssembleElementGrad( - elements, *transformation, element_state, element_matrices - ); + 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 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) { + auto relative_error = [](mfem::Vector computed, const mfem::Vector &reference) { computed -= reference; return computed.Norml2() / std::max(reference.Norml2(), 1.0e-30); }; @@ -221,10 +182,8 @@ TEST_CASE( 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_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; @@ -234,17 +193,11 @@ TEST_CASE( 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 - ); + 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_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; @@ -258,19 +211,11 @@ TEST_CASE( 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 - ); + 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_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; @@ -283,39 +228,19 @@ TEST_CASE( 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); + 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("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) - ); + 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); @@ -326,68 +251,46 @@ TEST_CASE( 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 - ); + 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); + const double minimal_density_difference = relative_error(minimal_density_action, field_coupled_density_action); - INFO( - "Minimal-mode density-block difference = " << minimal_density_difference - ); + 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) - ); + 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::unit &tags::solver &tags::integrator &tags::gravity ) { - constexpr int dim = 3; - constexpr double tolerance = 1.0e-12; + 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; + 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::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::FiniteElementSpace velocity_fes( - &mesh, &velocity_fec, dim, mfem::Ordering::byVDIM - ); + 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 displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; @@ -396,10 +299,9 @@ TEST_CASE( REQUIRE(domain_mapper.CalcIsIdentity()); - auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; + auto reference_density = [](const mfem::Vector &x) { return 1.0 + x(0); }; - auto reference_gravity_gradient = [](const mfem::Vector &x, - mfem::Vector &gradient) { + 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); @@ -407,29 +309,25 @@ TEST_CASE( }; mfem::FunctionCoefficient density_coefficient(reference_density); - mfem::VectorFunctionCoefficient gravity_gradient_coefficient( - dim, reference_gravity_gradient - ); + 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 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; + 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; @@ -438,9 +336,7 @@ TEST_CASE( 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 - ); + 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); @@ -483,23 +379,18 @@ TEST_CASE( domain_mapper, integrators::GravityForceJacobianMode::field_coupled ); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(velocity_element->GetGeomType(), 8); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(velocity_element->GetGeomType(), 8); integrator.SetIntegrationRule(integration_rule); - integrator.AssembleElementVector( - elements, *transformation, element_state, element_residual - ); + 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) { + 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; + test_dofs = 0.0; - const mfem::IntegrationRule &velocity_nodes = - velocity_element->GetNodes(); + 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); @@ -512,106 +403,62 @@ TEST_CASE( 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(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) - ); + 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 - ); + 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 - ); + 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) - ); + 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::unit &tags::solver &tags::integrator &tags::gravity ) { - 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 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; + 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::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::FiniteElementSpace velocity_fes( - &mesh, &velocity_fec, dim, mfem::Ordering::byVDIM - ); + 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 displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; @@ -635,20 +482,18 @@ TEST_CASE( 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 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; + 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; @@ -657,9 +502,7 @@ TEST_CASE( 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 - ); + 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); @@ -703,76 +546,54 @@ TEST_CASE( domain_mapper, integrators::GravityForceJacobianMode::field_coupled ); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(velocity_element->GetGeomType(), 8); + 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) { + 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 - ); + 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) { + 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); + 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); + 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); + 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); + 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); + 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( - zero_field_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance) + scaled_difference_norm(reversed_residual, base_residual, -1.0), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT( - scaled_difference_norm(reversed_residual, base_residual, -1.0), + scaled_difference_norm(gravity_scaled_residual, base_residual, gravity_scale), Catch::Matchers::WithinAbs(0.0, tolerance) ); CHECK_THAT( - scaled_difference_norm( - gravity_scaled_residual, base_residual, gravity_scale - ), + scaled_difference_norm(density_scaled_residual, base_residual, density_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 - ), + scaled_difference_norm(jointly_scaled_residual, base_residual, density_scale * gravity_scale), Catch::Matchers::WithinAbs(0.0, tolerance) ); @@ -781,10 +602,9 @@ TEST_CASE( mfem::Vector x_physical(dim); mfem::Vector centered_position(dim); mfem::Vector test_value(dim); - test_dofs = 0.0; + test_dofs = 0.0; - const mfem::IntegrationRule &velocity_nodes = - velocity_element->GetNodes(); + 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); @@ -798,87 +618,63 @@ TEST_CASE( test_function(centered_position, test_value); for (int component = 0; component < dim; ++component) { - test_dofs(i + component * velocity_dofs_count) = - test_value(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_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_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 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_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_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; - }); + 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) - ); + 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 - ); + mfem::Array2D element_matrices(block_count, block_count); for (int row = 0; row < block_count; ++row) { for (int column = 0; column < block_count; ++column) { @@ -891,19 +687,14 @@ TEST_CASE( element_state[density_block] = &density_dofs; element_state[gravity_gradient_block] = &zero_gravity; - integrator.AssembleElementGrad( - elements, *transformation, element_state, element_matrices - ); + 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(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) @@ -911,9 +702,7 @@ TEST_CASE( element_state[density_block] = &zero_density; element_state[gravity_gradient_block] = &gravity_gradient_dofs; - integrator.AssembleElementGrad( - elements, *transformation, element_state, element_matrices - ); + integrator.AssembleElementGrad(elements, *transformation, element_state, element_matrices); mfem::Vector zero_density_density_action(velocity_size); mfem::Vector zero_density_field_action(velocity_size); @@ -924,8 +713,5 @@ TEST_CASE( 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) - ); + CHECK_THAT(zero_density_field_action.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); } \ No newline at end of file diff --git a/tests/mapping/compactification/kelvin.cpp b/tests/mapping/compactification/kelvin.cpp index 997c502..48567e1 100644 --- a/tests/mapping/compactification/kelvin.cpp +++ b/tests/mapping/compactification/kelvin.cpp @@ -72,16 +72,9 @@ namespace { ) { const double radial_extent = r_inf - r_star; const double computational_radius = r_star + coordinate * radial_extent; - const double scale = - r_star / (computational_radius * (1.0 - coordinate)); - const double scale_derivative = - scale * - (1.0 / (1.0 - coordinate) - radial_extent / computational_radius); - return { - .computational_radius = computational_radius, - .scale = scale, - .scale_derivative = scale_derivative - }; + const double scale = r_star / (computational_radius * (1.0 - coordinate)); + const double scale_derivative = scale * (1.0 / (1.0 - coordinate) - radial_extent / computational_radius); + return {.computational_radius = computational_radius, .scale = scale, .scale_derivative = scale_derivative}; } mapping::MappingStatus evaluate_affine_map( @@ -98,12 +91,11 @@ namespace { displaced_position += offset; const mapping::compactification::ExteriorMapInput input{ - .reference_position = reference_position, - .displaced_position = displaced_position, - .displacement_jacobian = affine_jacobian, - .compactification_coordinate = compactification_coordinate, - .compactification_coordinate_gradient = - compactification_coordinate_gradient + .reference_position = reference_position, + .displaced_position = displaced_position, + .displacement_jacobian = affine_jacobian, + .compactification_coordinate = compactification_coordinate, + .compactification_coordinate_gradient = compactification_coordinate_gradient }; return exterior_map.Evaluate(input, result); @@ -114,47 +106,28 @@ TEST_CASE( "Kelvin Compactification Validates Its Configuration", tags::unit &tags::mapping &tags::kelvin ) { - CHECK_NOTHROW( - mapping::compactification::KelvinCompactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ) + CHECK_NOTHROW(mapping::compactification::KelvinCompactification({.r_star_ref = 1.0, .r_inf_ref = 4.0})); + CHECK_THROWS_AS( + mapping::compactification::KelvinCompactification({.r_star_ref = 0.0, .r_inf_ref = 4.0}), std::invalid_argument + ); + CHECK_THROWS_AS( + mapping::compactification::KelvinCompactification({.r_star_ref = -1.0, .r_inf_ref = 4.0}), std::invalid_argument + ); + CHECK_THROWS_AS( + mapping::compactification::KelvinCompactification({.r_star_ref = 2.0, .r_inf_ref = 2.0}), std::invalid_argument + ); + CHECK_THROWS_AS( + mapping::compactification::KelvinCompactification({.r_star_ref = 3.0, .r_inf_ref = 2.0}), std::invalid_argument ); CHECK_THROWS_AS( mapping::compactification::KelvinCompactification( - {.r_star_ref = 0.0, .r_inf_ref = 4.0} + {.r_star_ref = 1.0, .r_inf_ref = std::numeric_limits::infinity()} ), std::invalid_argument ); CHECK_THROWS_AS( mapping::compactification::KelvinCompactification( - {.r_star_ref = -1.0, .r_inf_ref = 4.0} - ), - std::invalid_argument - ); - CHECK_THROWS_AS( - mapping::compactification::KelvinCompactification( - {.r_star_ref = 2.0, .r_inf_ref = 2.0} - ), - std::invalid_argument - ); - CHECK_THROWS_AS( - mapping::compactification::KelvinCompactification( - {.r_star_ref = 3.0, .r_inf_ref = 2.0} - ), - std::invalid_argument - ); - CHECK_THROWS_AS( - mapping::compactification::KelvinCompactification( - {.r_star_ref = 1.0, - .r_inf_ref = std::numeric_limits::infinity()} - ), - std::invalid_argument - ); - CHECK_THROWS_AS( - mapping::compactification::KelvinCompactification( - {.r_star_ref = 1.0, - .r_inf_ref = 4.0, - .coordinate_tolerance = -1.0e-12} + {.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = -1.0e-12} ), std::invalid_argument ); @@ -166,9 +139,7 @@ TEST_CASE( ); CHECK_THROWS_AS( mapping::compactification::KelvinCompactification( - {.r_star_ref = 1.0, - .r_inf_ref = 4.0, - .coordinate_tolerance = std::numeric_limits::quiet_NaN()} + {.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = std::numeric_limits::quiet_NaN()} ), std::invalid_argument ); @@ -180,22 +151,13 @@ TEST_CASE( ) { constexpr double coordinate_tolerance = 3.0e-11; mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.25, - .r_inf_ref = 5.5, - .coordinate_tolerance = coordinate_tolerance} + {.r_star_ref = 1.25, .r_inf_ref = 5.5, .coordinate_tolerance = coordinate_tolerance} ); CHECK(compactification.GetName() == "KelvinCompactification"); - CHECK_THAT( - compactification.GetReferenceStellarRadius(), WithinAbs(1.25, 0.0) - ); - CHECK_THAT( - compactification.GetReferenceInfinityRadius(), WithinAbs(5.5, 0.0) - ); - CHECK_THAT( - compactification.GetCoordinateTolerance(), - WithinAbs(coordinate_tolerance, 0.0) - ); + CHECK_THAT(compactification.GetReferenceStellarRadius(), WithinAbs(1.25, 0.0)); + CHECK_THAT(compactification.GetReferenceInfinityRadius(), WithinAbs(5.5, 0.0)); + CHECK_THAT(compactification.GetCoordinateTolerance(), WithinAbs(coordinate_tolerance, 0.0)); } TEST_CASE( @@ -206,42 +168,28 @@ TEST_CASE( constexpr double tolerance = 0.0; constexpr double coordinate = 0.37; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::DenseMatrix displacement_jacobian = make_identity(); const mfem::Vector displaced_position = make_vector(1.4, -0.2, 0.3); - const mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05); - const mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1); - const mfem::Vector reference_b = make_vector(12.0, -7.0, 4.0); + const mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05); + const mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1); + const mfem::Vector reference_b = make_vector(12.0, -7.0, 4.0); const mapping::compactification::ExteriorMapInput input_a{ - reference_a, displaced_position, displacement_jacobian, coordinate, - coordinate_gradient + reference_a, displaced_position, displacement_jacobian, coordinate, coordinate_gradient }; const mapping::compactification::ExteriorMapInput input_b{ - reference_b, displaced_position, displacement_jacobian, coordinate, - coordinate_gradient + reference_b, displaced_position, displacement_jacobian, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result_a; mapping::compactification::ExteriorMapResult result_b; - REQUIRE( - compactification.Evaluate(input_a, result_a) == - mapping::MappingStatus::valid - ); - REQUIRE( - compactification.Evaluate(input_b, result_b) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input_a, result_a) == mapping::MappingStatus::valid); + REQUIRE(compactification.Evaluate(input_b, result_b) == mapping::MappingStatus::valid); - check_vector( - result_a.physical_position, result_b.physical_position, tolerance - ); - check_matrix( - result_a.mapping_jacobian, result_b.mapping_jacobian, tolerance - ); + check_vector(result_a.physical_position, result_b.physical_position, tolerance); + check_matrix(result_a.mapping_jacobian, result_b.mapping_jacobian, tolerance); } TEST_CASE( @@ -253,43 +201,28 @@ TEST_CASE( constexpr double radial_extent = r_inf - r_star; constexpr double tolerance = 2.0e-12; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = r_star, .r_inf_ref = r_inf} - ); - const mfem::DenseMatrix identity = make_identity(); - const mfem::Vector coordinate_gradient = - make_vector(1.0 / radial_extent, 0.0, 0.0); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = r_star, .r_inf_ref = r_inf}); + const mfem::DenseMatrix identity = make_identity(); + const mfem::Vector coordinate_gradient = make_vector(1.0 / radial_extent, 0.0, 0.0); - for (const double computational_radius : - std::array{1.0, 1.25, 2.0, 3.0, 3.75}) { + for (const double computational_radius : std::array{1.0, 1.25, 2.0, 3.0, 3.75}) { CAPTURE(computational_radius); - const double coordinate = - (computational_radius - r_star) / radial_extent; - const mfem::Vector reference_position = - make_vector(computational_radius, 0.0, 0.0); + const double coordinate = (computational_radius - r_star) / radial_extent; + const mfem::Vector reference_position = make_vector(computational_radius, 0.0, 0.0); const mfem::Vector displaced_position(reference_position); const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, identity, coordinate, - coordinate_gradient + reference_position, displaced_position, identity, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); - const double expected_radius = - r_star * radial_extent / (r_inf - computational_radius); - const double expected_radial_derivative = - r_star * radial_extent / - std::pow(r_inf - computational_radius, 2.0); - const double expected_tangential_scale = - expected_radius / computational_radius; + const double expected_radius = r_star * radial_extent / (r_inf - computational_radius); + const double expected_radial_derivative = r_star * radial_extent / std::pow(r_inf - computational_radius, 2.0); + const double expected_tangential_scale = expected_radius / computational_radius; - const mfem::Vector expected_position = - make_vector(expected_radius, 0.0, 0.0); + const mfem::Vector expected_position = make_vector(expected_radius, 0.0, 0.0); mfem::DenseMatrix expected_jacobian(dimension); expected_jacobian = 0.0; expected_jacobian(0, 0) = expected_radial_derivative; @@ -311,9 +244,7 @@ TEST_CASE( constexpr double coordinate = 0.42; constexpr double tolerance = 1.0e-12; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = r_star, .r_inf_ref = r_inf} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = r_star, .r_inf_ref = r_inf}); const mfem::Vector reference_position = make_vector(0.8, 0.4, -0.2); const mfem::Vector displaced_position = make_vector(1.1, 0.5, -0.1); const mfem::Vector coordinate_gradient = make_vector(0.20, -0.10, 0.05); @@ -330,18 +261,13 @@ TEST_CASE( displacement_jacobian(2, 2) = 1.05; const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, displacement_jacobian, - coordinate, coordinate_gradient + reference_position, displaced_position, displacement_jacobian, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); - const AnalyticFactors factors = - compute_analytic_factors(r_star, r_inf, coordinate); + const AnalyticFactors factors = compute_analytic_factors(r_star, r_inf, coordinate); mfem::Vector expected_position(displaced_position); expected_position *= factors.scale; @@ -350,9 +276,7 @@ TEST_CASE( for (int i = 0; i < dimension; ++i) { for (int j = 0; j < dimension; ++j) - expected_jacobian(i, j) += displaced_position(i) * - factors.scale_derivative * - coordinate_gradient(j); + expected_jacobian(i, j) += displaced_position(i) * factors.scale_derivative * coordinate_gradient(j); } check_vector(result.physical_position, expected_position, tolerance); @@ -367,9 +291,7 @@ TEST_CASE( constexpr double difference_step = 1.0e-6; constexpr double tolerance = 3.0e-9; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); mfem::DenseMatrix affine_jacobian(dimension); affine_jacobian(0, 0) = 1.05; @@ -389,8 +311,8 @@ TEST_CASE( mapping::compactification::ExteriorMapResult base_result; REQUIRE( evaluate_affine_map( - compactification, reference_position, affine_jacobian, offset, - base_coordinate, coordinate_gradient, base_result + compactification, reference_position, affine_jacobian, offset, base_coordinate, coordinate_gradient, + base_result ) == mapping::MappingStatus::valid ); @@ -400,39 +322,30 @@ TEST_CASE( reference_plus(coordinate) += difference_step; reference_minus(coordinate) -= difference_step; - const double compactification_plus = - base_coordinate + difference_step * coordinate_gradient(coordinate); - const double compactification_minus = - base_coordinate - difference_step * coordinate_gradient(coordinate); + const double compactification_plus = base_coordinate + difference_step * coordinate_gradient(coordinate); + const double compactification_minus = base_coordinate - difference_step * coordinate_gradient(coordinate); mapping::compactification::ExteriorMapResult result_plus; mapping::compactification::ExteriorMapResult result_minus; REQUIRE( evaluate_affine_map( - compactification, reference_plus, affine_jacobian, offset, - compactification_plus, coordinate_gradient, result_plus + compactification, reference_plus, affine_jacobian, offset, compactification_plus, coordinate_gradient, + result_plus ) == mapping::MappingStatus::valid ); REQUIRE( evaluate_affine_map( - compactification, reference_minus, affine_jacobian, offset, - compactification_minus, coordinate_gradient, result_minus + compactification, reference_minus, affine_jacobian, offset, compactification_minus, coordinate_gradient, + result_minus ) == mapping::MappingStatus::valid ); for (int component = 0; component < dimension; ++component) { const double finite_difference = - (result_plus.physical_position(component) - - result_minus.physical_position(component)) / + (result_plus.physical_position(component) - result_minus.physical_position(component)) / (2.0 * difference_step); - CHECK_THAT( - finite_difference, - WithinAbs( - base_result.mapping_jacobian(component, coordinate), - tolerance - ) - ); + CHECK_THAT(finite_difference, WithinAbs(base_result.mapping_jacobian(component, coordinate), tolerance)); } } } @@ -445,9 +358,7 @@ TEST_CASE( constexpr double difference_step = 1.0e-6; constexpr double tolerance = 2.0e-10; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::Vector reference_position = make_vector(1.3, -0.2, 0.4); const mfem::Vector displaced_position = make_vector(1.4, -0.1, 0.35); const mfem::Vector coordinate_gradient = make_vector(0.12, -0.04, 0.08); @@ -470,24 +381,16 @@ TEST_CASE( jacobian_direction(2, 2) = 0.01; const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, displacement_jacobian, - coordinate, coordinate_gradient - }; - const mapping::compactification::ExteriorMapDirection direction{ - position_direction, jacobian_direction + reference_position, displaced_position, displacement_jacobian, coordinate, coordinate_gradient }; + const mapping::compactification::ExteriorMapDirection direction{position_direction, jacobian_direction}; mapping::compactification::ExteriorMapResult base_result; mapping::compactification::ExteriorMapVariation variation; + REQUIRE(compactification.Evaluate(input, base_result) == mapping::MappingStatus::valid); REQUIRE( - compactification.Evaluate(input, base_result) == - mapping::MappingStatus::valid - ); - REQUIRE( - compactification.EvaluateVariation( - input, base_result, direction, variation - ) == mapping::MappingStatus::valid + compactification.EvaluateVariation(input, base_result, direction, variation) == mapping::MappingStatus::valid ); mfem::Vector displaced_plus(displaced_position); @@ -501,45 +404,27 @@ TEST_CASE( jacobian_minus.Add(-difference_step, jacobian_direction); const mapping::compactification::ExteriorMapInput input_plus{ - reference_position, displaced_plus, jacobian_plus, coordinate, - coordinate_gradient + reference_position, displaced_plus, jacobian_plus, coordinate, coordinate_gradient }; const mapping::compactification::ExteriorMapInput input_minus{ - reference_position, displaced_minus, jacobian_minus, coordinate, - coordinate_gradient + reference_position, displaced_minus, jacobian_minus, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result_plus; mapping::compactification::ExteriorMapResult result_minus; - REQUIRE( - compactification.Evaluate(input_plus, result_plus) == - mapping::MappingStatus::valid - ); - REQUIRE( - compactification.Evaluate(input_minus, result_minus) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input_plus, result_plus) == mapping::MappingStatus::valid); + REQUIRE(compactification.Evaluate(input_minus, result_minus) == mapping::MappingStatus::valid); for (int i = 0; i < dimension; ++i) { const double position_finite_difference = - (result_plus.physical_position(i) - - result_minus.physical_position(i)) / - (2.0 * difference_step); - CHECK_THAT( - position_finite_difference, - WithinAbs(variation.physical_position_variation(i), tolerance) - ); + (result_plus.physical_position(i) - result_minus.physical_position(i)) / (2.0 * difference_step); + CHECK_THAT(position_finite_difference, WithinAbs(variation.physical_position_variation(i), tolerance)); for (int j = 0; j < dimension; ++j) { const double jacobian_finite_difference = - (result_plus.mapping_jacobian(i, j) - - result_minus.mapping_jacobian(i, j)) / - (2.0 * difference_step); - CHECK_THAT( - jacobian_finite_difference, - WithinAbs(variation.mapping_jacobian_variation(i, j), tolerance) - ); + (result_plus.mapping_jacobian(i, j) - result_minus.mapping_jacobian(i, j)) / (2.0 * difference_step); + CHECK_THAT(jacobian_finite_difference, WithinAbs(variation.mapping_jacobian_variation(i, j), tolerance)); } } } @@ -551,9 +436,7 @@ TEST_CASE( constexpr double coordinate = 0.31; constexpr double tolerance = 1.0e-12; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::Vector reference_position = make_vector(1.3, 0.4, -0.2); const mfem::Vector displaced_position = make_vector(1.4, 0.2, -0.1); const mfem::Vector coordinate_gradient = make_vector(0.16, -0.08, 0.03); @@ -564,14 +447,10 @@ TEST_CASE( displacement_jacobian(2, 1) = 0.03; const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, displacement_jacobian, - coordinate, coordinate_gradient + reference_position, displaced_position, displacement_jacobian, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); mfem::DenseMatrix rotation(dimension); rotation = 0.0; @@ -592,14 +471,10 @@ TEST_CASE( mfem::MultABt(temporary, rotation, rotated_displacement_jacobian); const mapping::compactification::ExteriorMapInput rotated_input{ - rotated_reference, rotated_displaced, rotated_displacement_jacobian, - coordinate, rotated_coordinate_gradient + rotated_reference, rotated_displaced, rotated_displacement_jacobian, coordinate, rotated_coordinate_gradient }; mapping::compactification::ExteriorMapResult rotated_result; - REQUIRE( - compactification.Evaluate(rotated_input, rotated_result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(rotated_input, rotated_result) == mapping::MappingStatus::valid); mfem::Vector expected_position(dimension); rotation.Mult(result.physical_position, expected_position); @@ -608,9 +483,7 @@ TEST_CASE( mfem::Mult(rotation, result.mapping_jacobian, temporary); mfem::MultABt(temporary, rotation, expected_jacobian); - check_vector( - rotated_result.physical_position, expected_position, tolerance - ); + check_vector(rotated_result.physical_position, expected_position, tolerance); check_matrix(rotated_result.mapping_jacobian, expected_jacobian, tolerance); } @@ -620,12 +493,8 @@ TEST_CASE( ) { constexpr double tolerance = 1.0e-14; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); - const mfem::Vector reference_position = make_vector( - -0.5260553366425769, 0.5260553366425769, -0.6553163792879153 - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); + const mfem::Vector reference_position = make_vector(-0.5260553366425769, 0.5260553366425769, -0.6553163792879153); const mfem::Vector displaced_position = make_vector(-0.55, 0.51, -0.63); const mfem::Vector coordinate_gradient = make_vector(-0.18, 0.18, -0.22); const mfem::DenseMatrix displacement_jacobian = make_identity(); @@ -633,15 +502,11 @@ TEST_CASE( REQUIRE(reference_position.Norml2() < 1.0); const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, displacement_jacobian, 0.0, - coordinate_gradient + reference_position, displaced_position, displacement_jacobian, 0.0, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); check_vector(result.physical_position, displaced_position, tolerance); CHECK(result.mapping_jacobian.Det() > 0.0); } @@ -658,35 +523,23 @@ TEST_CASE( constexpr double tolerance = 2.0e-11; mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = r_star, - .r_inf_ref = r_inf, - .coordinate_tolerance = coordinate_tolerance} + {.r_star_ref = r_star, .r_inf_ref = r_inf, .coordinate_tolerance = coordinate_tolerance} ); - const mfem::DenseMatrix identity = make_identity(); - const mfem::Vector coordinate_gradient = - make_vector(1.0 / radial_extent, 0.0, 0.0); + const mfem::DenseMatrix identity = make_identity(); + const mfem::Vector coordinate_gradient = make_vector(1.0 / radial_extent, 0.0, 0.0); - for (const double coordinate : - std::array{0.0, 0.25, 0.75, 0.95, 0.99, 0.999}) { + for (const double coordinate : std::array{0.0, 0.25, 0.75, 0.95, 0.99, 0.999}) { CAPTURE(coordinate); - const double computational_radius = r_star + coordinate * radial_extent; - const mfem::Vector reference_position = - make_vector(computational_radius, 0.0, 0.0); + const double computational_radius = r_star + coordinate * radial_extent; + const mfem::Vector reference_position = make_vector(computational_radius, 0.0, 0.0); const mapping::compactification::ExteriorMapInput input{ - reference_position, reference_position, identity, coordinate, - coordinate_gradient + reference_position, reference_position, identity, coordinate, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); - CHECK_THAT( - result.physical_position.Norml2() * (1.0 - coordinate), - WithinAbs(r_star, tolerance) - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); + CHECK_THAT(result.physical_position.Norml2() * (1.0 - coordinate), WithinAbs(r_star, tolerance)); } const mfem::Vector reference_position = make_vector(r_inf, 0.0, 0.0); @@ -694,46 +547,37 @@ TEST_CASE( CHECK( compactification.Evaluate( - {reference_position, reference_position, identity, 1.0, - coordinate_gradient}, + {reference_position, reference_position, identity, 1.0, coordinate_gradient}, result + ) == mapping::MappingStatus::at_compactified_infinity + ); + CHECK( + compactification.Evaluate( + {reference_position, reference_position, identity, 1.0 - 0.5 * coordinate_tolerance, coordinate_gradient}, result ) == mapping::MappingStatus::at_compactified_infinity ); CHECK( compactification.Evaluate( - {reference_position, reference_position, identity, - 1.0 - 0.5 * coordinate_tolerance, coordinate_gradient}, + {reference_position, reference_position, identity, 1.0 + 0.5 * coordinate_tolerance, coordinate_gradient}, result ) == mapping::MappingStatus::at_compactified_infinity ); CHECK( compactification.Evaluate( - {reference_position, reference_position, identity, - 1.0 + 0.5 * coordinate_tolerance, coordinate_gradient}, - result - ) == mapping::MappingStatus::at_compactified_infinity - ); - CHECK( - compactification.Evaluate( - {reference_position, reference_position, identity, - 1.0 + 2.0 * coordinate_tolerance, coordinate_gradient}, + {reference_position, reference_position, identity, 1.0 + 2.0 * coordinate_tolerance, coordinate_gradient}, result ) == mapping::MappingStatus::outside_reference_domain ); CHECK( compactification.Evaluate( - {reference_position, reference_position, identity, - -2.0 * coordinate_tolerance, coordinate_gradient}, - result + {reference_position, reference_position, identity, -2.0 * coordinate_tolerance, coordinate_gradient}, result ) == mapping::MappingStatus::outside_reference_domain ); const mfem::Vector surface_position = make_vector(0.97, 0.0, 0.0); CHECK( compactification.Evaluate( - {surface_position, surface_position, identity, - -0.5 * coordinate_tolerance, coordinate_gradient}, - result + {surface_position, surface_position, identity, -0.5 * coordinate_tolerance, coordinate_gradient}, result ) == mapping::MappingStatus::valid ); check_vector(result.physical_position, surface_position, tolerance); @@ -743,9 +587,7 @@ TEST_CASE( "Kelvin Compactification Rejects Invalid Inputs And Inverted Maps", tags::unit &tags::mapping &tags::kelvin ) { - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0); const mfem::Vector displaced_position(reference_position); @@ -758,16 +600,12 @@ TEST_CASE( wrong_dimension = 1.0; CHECK( compactification.Evaluate( - {wrong_dimension, displaced_position, identity, 1.0 / 3.0, - coordinate_gradient}, - result + {wrong_dimension, displaced_position, identity, 1.0 / 3.0, coordinate_gradient}, result ) == mapping::MappingStatus::invalid_dimension ); CHECK( compactification.Evaluate( - {reference_position, displaced_position, identity, 1.0 / 3.0, - wrong_dimension}, - result + {reference_position, displaced_position, identity, 1.0 / 3.0, wrong_dimension}, result ) == mapping::MappingStatus::invalid_dimension ); @@ -775,9 +613,7 @@ TEST_CASE( non_finite_position(1) = std::numeric_limits::quiet_NaN(); CHECK( compactification.Evaluate( - {non_finite_position, displaced_position, identity, 1.0 / 3.0, - coordinate_gradient}, - result + {non_finite_position, displaced_position, identity, 1.0 / 3.0, coordinate_gradient}, result ) == mapping::MappingStatus::non_finite_input ); @@ -785,15 +621,13 @@ TEST_CASE( non_finite_gradient(2) = std::numeric_limits::infinity(); CHECK( compactification.Evaluate( - {reference_position, displaced_position, identity, 1.0 / 3.0, - non_finite_gradient}, - result + {reference_position, displaced_position, identity, 1.0 / 3.0, non_finite_gradient}, result ) == mapping::MappingStatus::non_finite_input ); CHECK( compactification.Evaluate( - {reference_position, displaced_position, identity, - std::numeric_limits::quiet_NaN(), coordinate_gradient}, + {reference_position, displaced_position, identity, std::numeric_limits::quiet_NaN(), + coordinate_gradient}, result ) == mapping::MappingStatus::non_finite_input ); @@ -802,9 +636,7 @@ TEST_CASE( singular_displacement_jacobian = 0.0; CHECK( compactification.Evaluate( - {reference_position, displaced_position, - singular_displacement_jacobian, 1.0 / 3.0, zero_gradient}, - result + {reference_position, displaced_position, singular_displacement_jacobian, 1.0 / 3.0, zero_gradient}, result ) == mapping::MappingStatus::non_positive_determinant ); @@ -812,9 +644,7 @@ TEST_CASE( inverted_displacement_jacobian(0, 0) = -1.0; CHECK( compactification.Evaluate( - {reference_position, displaced_position, - inverted_displacement_jacobian, 1.0 / 3.0, zero_gradient}, - result + {reference_position, displaced_position, inverted_displacement_jacobian, 1.0 / 3.0, zero_gradient}, result ) == mapping::MappingStatus::non_positive_determinant ); } @@ -823,44 +653,34 @@ TEST_CASE( "Kelvin Compactification Variation Rejects Invalid Inputs", tags::unit &tags::mapping &tags::kelvin ) { - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0); const mfem::Vector displaced_position(reference_position); const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0); const mfem::DenseMatrix identity = make_identity(); const mapping::compactification::ExteriorMapInput input{ - reference_position, displaced_position, identity, 1.0 / 3.0, - coordinate_gradient + reference_position, displaced_position, identity, 1.0 / 3.0, coordinate_gradient }; mapping::compactification::ExteriorMapResult result; - REQUIRE( - compactification.Evaluate(input, result) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input, result) == mapping::MappingStatus::valid); - const mfem::Vector valid_position_direction = - make_vector(0.01, -0.02, 0.03); + const mfem::Vector valid_position_direction = make_vector(0.01, -0.02, 0.03); const mfem::DenseMatrix valid_jacobian_direction = make_identity(); mapping::compactification::ExteriorMapVariation variation; mfem::Vector wrong_dimension(2); wrong_dimension = 0.0; CHECK( - compactification.EvaluateVariation( - input, result, {wrong_dimension, valid_jacobian_direction}, - variation - ) == mapping::MappingStatus::invalid_dimension + compactification.EvaluateVariation(input, result, {wrong_dimension, valid_jacobian_direction}, variation) == + mapping::MappingStatus::invalid_dimension ); mfem::DenseMatrix wrong_jacobian_dimension(2); wrong_jacobian_dimension = 0.0; CHECK( compactification.EvaluateVariation( - input, result, {valid_position_direction, wrong_jacobian_dimension}, - variation + input, result, {valid_position_direction, wrong_jacobian_dimension}, variation ) == mapping::MappingStatus::invalid_dimension ); @@ -868,8 +688,7 @@ TEST_CASE( non_finite_direction(0) = std::numeric_limits::quiet_NaN(); CHECK( compactification.EvaluateVariation( - input, result, {non_finite_direction, valid_jacobian_direction}, - variation + input, result, {non_finite_direction, valid_jacobian_direction}, variation ) == mapping::MappingStatus::non_finite_input ); } @@ -880,43 +699,24 @@ TEST_CASE( ) { constexpr double tolerance = 0.0; - mapping::compactification::KelvinCompactification compactification( - {.r_star_ref = 1.0, .r_inf_ref = 4.0} - ); + mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}); const mfem::DenseMatrix identity = make_identity(); const mfem::Vector gradient_a = make_vector(0.12, 0.03, -0.02); const mfem::Vector gradient_b = make_vector(-0.04, 0.15, 0.01); const mfem::Vector reference_a = make_vector(1.5, 0.2, 0.1); const mfem::Vector reference_b = make_vector(2.5, -0.3, 0.4); - const mapping::compactification::ExteriorMapInput input_a{ - reference_a, reference_a, identity, 0.25, gradient_a - }; - const mapping::compactification::ExteriorMapInput input_b{ - reference_b, reference_b, identity, 0.70, gradient_b - }; + const mapping::compactification::ExteriorMapInput input_a{reference_a, reference_a, identity, 0.25, gradient_a}; + const mapping::compactification::ExteriorMapInput input_b{reference_b, reference_b, identity, 0.70, gradient_b}; mapping::compactification::ExteriorMapResult first_a; mapping::compactification::ExteriorMapResult result_b; mapping::compactification::ExteriorMapResult second_a; - REQUIRE( - compactification.Evaluate(input_a, first_a) == - mapping::MappingStatus::valid - ); - REQUIRE( - compactification.Evaluate(input_b, result_b) == - mapping::MappingStatus::valid - ); - REQUIRE( - compactification.Evaluate(input_a, second_a) == - mapping::MappingStatus::valid - ); + REQUIRE(compactification.Evaluate(input_a, first_a) == mapping::MappingStatus::valid); + REQUIRE(compactification.Evaluate(input_b, result_b) == mapping::MappingStatus::valid); + REQUIRE(compactification.Evaluate(input_a, second_a) == mapping::MappingStatus::valid); - check_vector( - first_a.physical_position, second_a.physical_position, tolerance - ); - check_matrix( - first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance - ); + check_vector(first_a.physical_position, second_a.physical_position, tolerance); + check_matrix(first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance); } \ No newline at end of file diff --git a/tests/mapping/domain_mapper.cpp b/tests/mapping/domain_mapper.cpp index 91d7857..faa492e 100644 --- a/tests/mapping/domain_mapper.cpp +++ b/tests/mapping/domain_mapper.cpp @@ -16,13 +16,9 @@ namespace { constexpr int dimension = 3; constexpr double tolerance = 1.0e-12; - std::unique_ptr - make_kelvin_compactification() { - return std::make_unique< - mapping::compactification::KelvinCompactification>( - mapping::compactification::options::KelvinCompactificationOptions{ - .r_star_ref = 1.0, .r_inf_ref = 4.0 - } + std::unique_ptr make_kelvin_compactification() { + return std::make_unique( + mapping::compactification::options::KelvinCompactificationOptions{.r_star_ref = 1.0, .r_inf_ref = 4.0} ); } @@ -65,9 +61,7 @@ namespace { class ElementMappingDataOwner { public: - explicit ElementMappingDataOwner( - const mapping::ElementDisplacementData &displacement - ) + explicit ElementMappingDataOwner(const mapping::ElementDisplacementData &displacement) : m_compactification( displacement.GetElement(), make_constant_compactification_dofs(displacement.GetElement()) @@ -93,11 +87,10 @@ namespace { } { } - ElementMappingDataOwner(const ElementMappingDataOwner &) = delete; - ElementMappingDataOwner & - operator=(const ElementMappingDataOwner &) = delete; - ElementMappingDataOwner(ElementMappingDataOwner &&) = delete; - ElementMappingDataOwner &operator=(ElementMappingDataOwner &&) = delete; + ElementMappingDataOwner(const ElementMappingDataOwner &) = delete; + ElementMappingDataOwner &operator=(const ElementMappingDataOwner &) = delete; + ElementMappingDataOwner(ElementMappingDataOwner &&) = delete; + ElementMappingDataOwner &operator=(ElementMappingDataOwner &&) = delete; [[nodiscard]] const mapping::ElementMappingData &Get() const noexcept { return m_element_data; @@ -128,10 +121,7 @@ namespace { for (int i = 0; i < actual.Height(); ++i) { for (int j = 0; j < actual.Width(); ++j) - CHECK_THAT( - actual(i, j), - WithinAbs(expected(i, j), comparison_tolerance) - ); + CHECK_THAT(actual(i, j), WithinAbs(expected(i, j), comparison_tolerance)); } } @@ -224,9 +214,8 @@ namespace { displacement += displacement_offset; for (int component = 0; component < field_dimension; ++component) { - const int index = ordering == mfem::Ordering::byNODES - ? i + component * dof_count - : component + i * field_dimension; + const int index = + ordering == mfem::Ordering::byNODES ? i + component * dof_count : component + i * field_dimension; element_dofs(index) = displacement(component); } } @@ -234,10 +223,8 @@ namespace { return element_dofs; } - mfem::DenseMatrix - make_deformation_jacobian(const mfem::DenseMatrix &displacement_gradient) { - mfem::DenseMatrix deformation_jacobian = - make_identity_matrix(displacement_gradient.Height()); + mfem::DenseMatrix make_deformation_jacobian(const mfem::DenseMatrix &displacement_gradient) { + mfem::DenseMatrix deformation_jacobian = make_identity_matrix(displacement_gradient.Height()); deformation_jacobian.Add(1.0, displacement_gradient); return deformation_jacobian; } @@ -261,34 +248,13 @@ namespace { const double comparison_tolerance = tolerance ) { CHECK(actual.compactified == expected.compactified); - check_vector( - actual.reference_position, expected.reference_position, - comparison_tolerance - ); - check_vector( - actual.displaced_position, expected.displaced_position, - comparison_tolerance - ); - check_vector( - actual.physical_position, expected.physical_position, - comparison_tolerance - ); - check_matrix( - actual.displacement_jacobian, expected.displacement_jacobian, - comparison_tolerance - ); - check_matrix( - actual.mapping_jacobian, expected.mapping_jacobian, - comparison_tolerance - ); - check_matrix( - actual.inverse_mapping_jacobian, expected.inverse_mapping_jacobian, - comparison_tolerance - ); - CHECK_THAT( - actual.mapping_determinant, - WithinAbs(expected.mapping_determinant, comparison_tolerance) - ); + check_vector(actual.reference_position, expected.reference_position, comparison_tolerance); + check_vector(actual.displaced_position, expected.displaced_position, comparison_tolerance); + check_vector(actual.physical_position, expected.physical_position, comparison_tolerance); + check_matrix(actual.displacement_jacobian, expected.displacement_jacobian, comparison_tolerance); + check_matrix(actual.mapping_jacobian, expected.mapping_jacobian, comparison_tolerance); + check_matrix(actual.inverse_mapping_jacobian, expected.inverse_mapping_jacobian, comparison_tolerance); + CHECK_THAT(actual.mapping_determinant, WithinAbs(expected.mapping_determinant, comparison_tolerance)); } constexpr double polynomial_tolerance = 2.0e-11; @@ -349,9 +315,8 @@ namespace { function(reference_position, value); for (int component = 0; component < dimension; ++component) { - const int index = ordering == mfem::Ordering::byNODES - ? i + component * dof_count - : component + i * dimension; + const int index = + ordering == mfem::Ordering::byNODES ? i + component * dof_count : component + i * dimension; element_dofs(index) = value(component); } } @@ -373,8 +338,7 @@ namespace { displacement(2) = 0.015 + 0.003 * x * z - 0.002 * y * y; } - mfem::DenseMatrix - evaluate_quadratic_displacement_gradient(const mfem::Vector &position) { + mfem::DenseMatrix evaluate_quadratic_displacement_gradient(const mfem::Vector &position) { const double x = position(0); const double y = position(1); const double z = position(2); @@ -406,8 +370,7 @@ namespace { direction(2) = 0.010 * z + 0.004 * x * y; } - mfem::DenseMatrix - evaluate_quadratic_direction_gradient(const mfem::Vector &position) { + mfem::DenseMatrix evaluate_quadratic_direction_gradient(const mfem::Vector &position) { const double x = position(0); const double y = position(1); const double z = position(2); @@ -436,11 +399,8 @@ namespace { REQUIRE(plus.Size() == expected.Size()); for (int i = 0; i < expected.Size(); ++i) { - const double finite_difference = - (plus(i) - minus(i)) / (2.0 * step); - CHECK_THAT( - finite_difference, WithinAbs(expected(i), comparison_tolerance) - ); + const double finite_difference = (plus(i) - minus(i)) / (2.0 * step); + CHECK_THAT(finite_difference, WithinAbs(expected(i), comparison_tolerance)); } } @@ -458,12 +418,8 @@ namespace { for (int i = 0; i < expected.Height(); ++i) { for (int j = 0; j < expected.Width(); ++j) { - const double finite_difference = - (plus(i, j) - minus(i, j)) / (2.0 * step); - CHECK_THAT( - finite_difference, - WithinAbs(expected(i, j), comparison_tolerance) - ); + const double finite_difference = (plus(i, j) - minus(i, j)) / (2.0 * step); + CHECK_THAT(finite_difference, WithinAbs(expected(i, j), comparison_tolerance)); } } } @@ -479,12 +435,10 @@ namespace { mfem::ElementTransformation &transformation ) { base_dofs = make_function_element_dofs( - element, transformation, evaluate_quadratic_displacement, - mfem::Ordering::byVDIM + element, transformation, evaluate_quadratic_displacement, mfem::Ordering::byVDIM ); direction_dofs = make_function_element_dofs( - element, transformation, evaluate_quadratic_direction, - mfem::Ordering::byVDIM + element, transformation, evaluate_quadratic_direction, mfem::Ordering::byVDIM ); plus_dofs = base_dofs; minus_dofs = base_dofs; @@ -499,12 +453,7 @@ namespace { const double relative_tolerance, const double absolute_tolerance = 1.0e-11 ) { - CHECK_THAT( - actual, WithinAbs( - expected, absolute_tolerance + - relative_tolerance * std::abs(expected) - ) - ); + CHECK_THAT(actual, WithinAbs(expected, absolute_tolerance + relative_tolerance * std::abs(expected))); } void check_vector_central_difference_relative( @@ -518,11 +467,8 @@ namespace { REQUIRE(plus.Size() == expected.Size()); for (int i = 0; i < expected.Size(); ++i) { - const double finite_difference = - (plus(i) - minus(i)) / (2.0 * step); - check_scalar_relative( - finite_difference, expected(i), relative_tolerance - ); + const double finite_difference = (plus(i) - minus(i)) / (2.0 * step); + check_scalar_relative(finite_difference, expected(i), relative_tolerance); } } @@ -540,11 +486,8 @@ namespace { for (int i = 0; i < expected.Height(); ++i) { for (int j = 0; j < expected.Width(); ++j) { - const double finite_difference = - (plus(i, j) - minus(i, j)) / (2.0 * step); - check_scalar_relative( - finite_difference, expected(i, j), relative_tolerance - ); + const double finite_difference = (plus(i, j) - minus(i, j)) / (2.0 * step); + check_scalar_relative(finite_difference, expected(i, j), relative_tolerance); } } } @@ -562,8 +505,7 @@ namespace { mfem::Vector matrix_curl(const mfem::DenseMatrix &gradient) { return make_vector( - gradient(2, 1) - gradient(1, 2), gradient(0, 2) - gradient(2, 0), - gradient(1, 0) - gradient(0, 1) + gradient(2, 1) - gradient(1, 2), gradient(0, 2) - gradient(2, 0), gradient(1, 0) - gradient(0, 1) ); } @@ -576,19 +518,12 @@ namespace { const double relative_tolerance = 2.0e-6, const double absolute_tolerance = 1.0e-11 ) { - const double derivative_scale = - std::max(std::abs(analytic), std::abs(finite_difference)); - const double primal_scale = - std::max(std::abs(plus_value), std::abs(minus_value)); - const double roundoff_tolerance = - 8.0 * std::numeric_limits::epsilon() * primal_scale / step; - const double tolerance = absolute_tolerance + - relative_tolerance * derivative_scale + - roundoff_tolerance; + const double derivative_scale = std::max(std::abs(analytic), std::abs(finite_difference)); + const double primal_scale = std::max(std::abs(plus_value), std::abs(minus_value)); + const double roundoff_tolerance = 8.0 * std::numeric_limits::epsilon() * primal_scale / step; + const double tolerance = absolute_tolerance + relative_tolerance * derivative_scale + roundoff_tolerance; - CHECK_THAT( - finite_difference, Catch::Matchers::WithinAbs(analytic, tolerance) - ); + CHECK_THAT(finite_difference, Catch::Matchers::WithinAbs(analytic, tolerance)); } double relative_vector_difference( @@ -628,18 +563,14 @@ TEST_CASE( for (int i = 0; i < dof_count; ++i) { for (int component = 0; component < dimension; ++component) { - const double value = 100.0 * component + i + 1.0; + const double value = 100.0 * component + i + 1.0; by_vdim_dofs(component + i * dimension) = value; by_nodes_dofs(i + component * dof_count) = value; } } - const mapping::ElementDisplacementData by_vdim_data( - element, by_vdim_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData by_nodes_data( - element, by_nodes_dofs, mfem::Ordering::byNODES - ); + const mapping::ElementDisplacementData by_vdim_data(element, by_vdim_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData by_nodes_data(element, by_nodes_dofs, mfem::Ordering::byNODES); REQUIRE(&by_vdim_data.GetElement() == &element); REQUIRE(&by_nodes_data.GetElement() == &element); @@ -650,16 +581,11 @@ TEST_CASE( REQUIRE(by_vdim_data.GetOrdering() == mfem::Ordering::byVDIM); REQUIRE(by_nodes_data.GetOrdering() == mfem::Ordering::byNODES); - check_matrix( - by_vdim_data.GetDofMatrix(), by_nodes_data.GetDofMatrix(), 0.0 - ); + check_matrix(by_vdim_data.GetDofMatrix(), by_nodes_data.GetDofMatrix(), 0.0); for (int i = 0; i < dof_count; ++i) { for (int component = 0; component < dimension; ++component) { - CHECK_THAT( - by_vdim_data.GetDofMatrix()(i, component), - WithinAbs(100.0 * component + i + 1.0, 0.0) - ); + CHECK_THAT(by_vdim_data.GetDofMatrix()(i, component), WithinAbs(100.0 * component + i + 1.0, 0.0)); } } } @@ -678,16 +604,10 @@ TEST_CASE( incomplete_dofs = 0.0; CHECK_THROWS_AS( - mapping::ElementDisplacementData( - element, empty_dofs, mfem::Ordering::byVDIM - ), - std::invalid_argument + mapping::ElementDisplacementData(element, empty_dofs, mfem::Ordering::byVDIM), std::invalid_argument ); CHECK_THROWS_AS( - mapping::ElementDisplacementData( - element, incomplete_dofs, mfem::Ordering::byVDIM - ), - std::invalid_argument + mapping::ElementDisplacementData(element, incomplete_dofs, mfem::Ordering::byVDIM), std::invalid_argument ); } @@ -707,54 +627,32 @@ TEST_CASE( CHECK_THROWS_AS(workspace.SetDimension(0), std::invalid_argument); CHECK_THROWS_AS(workspace.SetDimension(-1), std::invalid_argument); - CHECK_THROWS_AS( - mapping::DomainMapperStateless::Workspace(0), std::invalid_argument - ); + CHECK_THROWS_AS(mapping::DomainMapperStateless::Workspace(0), std::invalid_argument); } TEST_CASE( "Stateless Domain Mapper Validates Its Configuration", tags::unit &tags::mapping ) { - const utils::DomainMapperStatelessOptions valid_options{ - .dimension = dimension, .vacuum_element_attribute = 3 - }; - mapping::DomainMapperStateless mapper( - valid_options, make_kelvin_compactification() - ); + const utils::DomainMapperStatelessOptions valid_options{.dimension = dimension, .vacuum_element_attribute = 3}; + mapping::DomainMapperStateless mapper(valid_options, make_kelvin_compactification()); REQUIRE(mapper.GetDimension() == dimension); REQUIRE(mapper.GetVacuumElementAttribute() == 3); REQUIRE(mapper.GetExteriorMap().GetName() == "KelvinCompactification"); - const utils::DomainMapperStatelessOptions invalid_dimension{ - .dimension = 0, .vacuum_element_attribute = 3 - }; - const utils::DomainMapperStatelessOptions invalid_attribute{ - .dimension = dimension, .vacuum_element_attribute = 0 - }; + const utils::DomainMapperStatelessOptions invalid_dimension{.dimension = 0, .vacuum_element_attribute = 3}; + const utils::DomainMapperStatelessOptions invalid_attribute{.dimension = dimension, .vacuum_element_attribute = 0}; CHECK_THROWS_AS( - mapping::DomainMapperStateless( - invalid_dimension, make_kelvin_compactification() - ), - std::invalid_argument + mapping::DomainMapperStateless(invalid_dimension, make_kelvin_compactification()), std::invalid_argument ); CHECK_THROWS_AS( - mapping::DomainMapperStateless( - invalid_attribute, make_kelvin_compactification() - ), - std::invalid_argument + mapping::DomainMapperStateless(invalid_attribute, make_kelvin_compactification()), std::invalid_argument ); - std::unique_ptr - null_exterior_map; - CHECK_THROWS_AS( - mapping::DomainMapperStateless( - valid_options, std::move(null_exterior_map) - ), - std::invalid_argument - ); + std::unique_ptr null_exterior_map; + CHECK_THROWS_AS(mapping::DomainMapperStateless(valid_options, std::move(null_exterior_map)), std::invalid_argument); } TEST_CASE( @@ -765,37 +663,29 @@ TEST_CASE( const mfem::FiniteElement &element = fixture.GetElement(); const mfem::Vector zero_dofs = fixture.MakeZeroElementDofs(); - const mapping::ElementDisplacementData displacement( - element, zero_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData displacement(element, zero_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::MappingPointContext context; - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); REQUIRE_FALSE(mapper.IsCompactifiedElement(*transformation)); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 4); - const mfem::DenseMatrix identity = make_identity_matrix(dimension); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 4); + const mfem::DenseMatrix identity = make_identity_matrix(dimension); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mfem::Vector expected_position(dimension); transformation->Transform(integration_point, expected_position); REQUIRE( - mapper.EvaluatePoint( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); CAPTURE(q); @@ -818,32 +708,24 @@ TEST_CASE( const mfem::FiniteElement &element = fixture.GetElement(); const mfem::Vector zero_dofs = fixture.MakeZeroElementDofs(); - const mapping::ElementDisplacementData displacement( - element, zero_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData displacement(element, zero_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::VolumeMappingContext context; - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 4); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 4); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); REQUIRE( - mapper.EvaluateVolume( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); transformation->SetIntPoint(&integration_point); @@ -851,16 +733,13 @@ TEST_CASE( mfem::DenseMatrix expected_inverse(dimension); mfem::CalcInverse(transformation->Jacobian(), expected_inverse); - const double expected_weight = - integration_point.weight * transformation->Weight(); + const double expected_weight = integration_point.weight * transformation->Weight(); CAPTURE(q); REQUIRE_FALSE(context.mapping.compactified); check_matrix(context.quadrature.J_inv, expected_inverse); CHECK_THAT(context.quadrature.detJ, WithinAbs(1.0, tolerance)); - CHECK_THAT( - context.quadrature.weight, WithinAbs(expected_weight, tolerance) - ); + CHECK_THAT(context.quadrature.weight, WithinAbs(expected_weight, tolerance)); CHECK(context.quadrature.weight > 0.0); } } @@ -873,36 +752,28 @@ TEST_CASE( const mfem::FiniteElement &element = fixture.GetElement(); const mfem::Vector zero_dofs = fixture.MakeZeroElementDofs(); - const mapping::ElementDisplacementData displacement( - element, zero_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData displacement(element, zero_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::FaceMappingContext context; - for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *transformation = - fixture.mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *transformation = fixture.mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(transformation != nullptr); REQUIRE(transformation->Elem1 != nullptr); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 4); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 4); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); REQUIRE( mapper.EvaluateFace( - element_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + element_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, context ) == mapping::MappingStatus::valid ); @@ -916,16 +787,12 @@ TEST_CASE( mfem::Vector expected_normal(raw_normal); expected_normal /= raw_normal_magnitude; - const double expected_surface_weight = - integration_point.weight * raw_normal_magnitude; + const double expected_surface_weight = integration_point.weight * raw_normal_magnitude; - const mfem::IntegrationPoint element_integration_point = - transformation->Elem1->GetIntPoint(); + const mfem::IntegrationPoint element_integration_point = transformation->Elem1->GetIntPoint(); mfem::Vector expected_position(dimension); - transformation->Elem1->Transform( - element_integration_point, expected_position - ); + transformation->Elem1->Transform(element_integration_point, expected_position); CAPTURE(boundary_element, q); REQUIRE_FALSE(context.mapping.compactified); @@ -934,21 +801,10 @@ TEST_CASE( check_vector(context.mapping.physical_position, expected_position); check_vector(context.reference_normal, expected_normal); check_vector(context.quadrature.normal, expected_normal); - CHECK_THAT( - context.reference_surface_weight, - WithinAbs(expected_surface_weight, tolerance) - ); - CHECK_THAT( - context.physical_surface_weight, - WithinAbs(expected_surface_weight, tolerance) - ); - CHECK_THAT( - context.quadrature.ds, - WithinAbs(expected_surface_weight, tolerance) - ); - CHECK_THAT( - context.quadrature.v_dot_n_scale, WithinAbs(1.0, tolerance) - ); + CHECK_THAT(context.reference_surface_weight, WithinAbs(expected_surface_weight, tolerance)); + CHECK_THAT(context.physical_surface_weight, WithinAbs(expected_surface_weight, tolerance)); + CHECK_THAT(context.quadrature.ds, WithinAbs(expected_surface_weight, tolerance)); + CHECK_THAT(context.quadrature.v_dot_n_scale, WithinAbs(1.0, tolerance)); } } } @@ -959,27 +815,20 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix displacement_gradient = - make_affine_displacement_gradient(); - const mfem::DenseMatrix deformation_jacobian = - make_deformation_jacobian(displacement_gradient); - const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); - const mfem::Vector element_dofs = make_affine_element_dofs( - element, *transformation, displacement_gradient, displacement_offset, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, element_dofs, mfem::Ordering::byVDIM + const mfem::DenseMatrix displacement_gradient = make_affine_displacement_gradient(); + const mfem::DenseMatrix deformation_jacobian = make_deformation_jacobian(displacement_gradient); + const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); + const mfem::Vector element_dofs = make_affine_element_dofs( + element, *transformation, displacement_gradient, displacement_offset, mfem::Ordering::byVDIM ); + const mapping::ElementDisplacementData displacement(element, element_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::MappingPointContext context; @@ -990,25 +839,19 @@ TEST_CASE( REQUIRE(deformation_determinant > 0.0); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mfem::Vector reference_position(dimension); transformation->Transform(integration_point, reference_position); const mfem::Vector expected_position = - evaluate_affine_physical_position( - reference_position, displacement_gradient, displacement_offset - ); + evaluate_affine_physical_position(reference_position, displacement_gradient, displacement_offset); REQUIRE( - mapper.EvaluatePoint( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); CAPTURE(q); @@ -1018,13 +861,8 @@ TEST_CASE( check_vector(context.physical_position, expected_position); check_matrix(context.displacement_jacobian, deformation_jacobian); check_matrix(context.mapping_jacobian, deformation_jacobian); - check_matrix( - context.inverse_mapping_jacobian, inverse_deformation_jacobian - ); - CHECK_THAT( - context.mapping_determinant, - WithinAbs(deformation_determinant, tolerance) - ); + check_matrix(context.inverse_mapping_jacobian, inverse_deformation_jacobian); + CHECK_THAT(context.mapping_determinant, WithinAbs(deformation_determinant, tolerance)); } } @@ -1035,74 +873,52 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix displacement_gradient = - make_affine_displacement_gradient(); - const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); + const mfem::DenseMatrix displacement_gradient = make_affine_displacement_gradient(); + const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); - const mfem::Vector by_vdim_dofs = make_affine_element_dofs( - element, *transformation, displacement_gradient, displacement_offset, - mfem::Ordering::byVDIM + const mfem::Vector by_vdim_dofs = make_affine_element_dofs( + element, *transformation, displacement_gradient, displacement_offset, mfem::Ordering::byVDIM ); const mfem::Vector by_nodes_dofs = make_affine_element_dofs( - element, *transformation, displacement_gradient, displacement_offset, - mfem::Ordering::byNODES + element, *transformation, displacement_gradient, displacement_offset, mfem::Ordering::byNODES ); - const mapping::ElementDisplacementData by_vdim_displacement( - element, by_vdim_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData by_nodes_displacement( - element, by_nodes_dofs, mfem::Ordering::byNODES - ); + const mapping::ElementDisplacementData by_vdim_displacement(element, by_vdim_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData by_nodes_displacement(element, by_nodes_dofs, mfem::Ordering::byNODES); const ElementMappingDataOwner by_vdim_data(by_vdim_displacement); const ElementMappingDataOwner by_nodes_data(by_nodes_displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::VolumeMappingContext by_vdim_context; mapping::VolumeMappingContext by_nodes_context; - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); REQUIRE( - mapper.EvaluateVolume( - by_vdim_data.Get(), *transformation, integration_point, - workspace, by_vdim_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(by_vdim_data.Get(), *transformation, integration_point, workspace, by_vdim_context) == + mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateVolume( - by_nodes_data.Get(), *transformation, integration_point, - workspace, by_nodes_context + by_nodes_data.Get(), *transformation, integration_point, workspace, by_nodes_context ) == mapping::MappingStatus::valid ); CAPTURE(q); check_point_context(by_vdim_context.mapping, by_nodes_context.mapping); - check_matrix( - by_vdim_context.quadrature.J_inv, by_nodes_context.quadrature.J_inv - ); - CHECK_THAT( - by_vdim_context.quadrature.detJ, - WithinAbs(by_nodes_context.quadrature.detJ, tolerance) - ); - CHECK_THAT( - by_vdim_context.quadrature.weight, - WithinAbs(by_nodes_context.quadrature.weight, tolerance) - ); + check_matrix(by_vdim_context.quadrature.J_inv, by_nodes_context.quadrature.J_inv); + CHECK_THAT(by_vdim_context.quadrature.detJ, WithinAbs(by_nodes_context.quadrature.detJ, tolerance)); + CHECK_THAT(by_vdim_context.quadrature.weight, WithinAbs(by_nodes_context.quadrature.weight, tolerance)); } } @@ -1112,27 +928,20 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix displacement_gradient = - make_affine_displacement_gradient(); - const mfem::DenseMatrix mapping_jacobian = - make_deformation_jacobian(displacement_gradient); - const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); - const mfem::Vector element_dofs = make_affine_element_dofs( - element, *transformation, displacement_gradient, displacement_offset, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, element_dofs, mfem::Ordering::byVDIM + const mfem::DenseMatrix displacement_gradient = make_affine_displacement_gradient(); + const mfem::DenseMatrix mapping_jacobian = make_deformation_jacobian(displacement_gradient); + const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); + const mfem::Vector element_dofs = make_affine_element_dofs( + element, *transformation, displacement_gradient, displacement_offset, mfem::Ordering::byVDIM ); + const mapping::ElementDisplacementData displacement(element, element_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::VolumeMappingContext context; @@ -1140,41 +949,29 @@ TEST_CASE( const double mapping_determinant = mapping_jacobian.Det(); REQUIRE(mapping_determinant > 0.0); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); mfem::DenseMatrix full_element_jacobian(dimension); mfem::DenseMatrix expected_inverse(dimension); - mfem::Mult( - mapping_jacobian, transformation->Jacobian(), full_element_jacobian - ); + mfem::Mult(mapping_jacobian, transformation->Jacobian(), full_element_jacobian); mfem::CalcInverse(full_element_jacobian, expected_inverse); - const double expected_weight = integration_point.weight * - transformation->Weight() * - mapping_determinant; + const double expected_weight = integration_point.weight * transformation->Weight() * mapping_determinant; REQUIRE( - mapper.EvaluateVolume( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); CAPTURE(q); check_matrix(context.mapping.mapping_jacobian, mapping_jacobian); check_matrix(context.quadrature.J_inv, expected_inverse); - CHECK_THAT( - context.quadrature.detJ, WithinAbs(mapping_determinant, tolerance) - ); - CHECK_THAT( - context.quadrature.weight, WithinAbs(expected_weight, tolerance) - ); + CHECK_THAT(context.quadrature.detJ, WithinAbs(mapping_determinant, tolerance)); + CHECK_THAT(context.quadrature.weight, WithinAbs(expected_weight, tolerance)); } } @@ -1184,22 +981,16 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *element_transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *element_transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix displacement_gradient = - make_affine_displacement_gradient(); - const mfem::DenseMatrix mapping_jacobian = - make_deformation_jacobian(displacement_gradient); - const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); - const mfem::Vector element_dofs = make_affine_element_dofs( - element, *element_transformation, displacement_gradient, - displacement_offset, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, element_dofs, mfem::Ordering::byVDIM + const mfem::DenseMatrix displacement_gradient = make_affine_displacement_gradient(); + const mfem::DenseMatrix mapping_jacobian = make_deformation_jacobian(displacement_gradient); + const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); + const mfem::Vector element_dofs = make_affine_element_dofs( + element, *element_transformation, displacement_gradient, displacement_offset, mfem::Ordering::byVDIM ); + const mapping::ElementDisplacementData displacement(element, element_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mfem::DenseMatrix inverse_mapping_jacobian(dimension); @@ -1207,25 +998,20 @@ TEST_CASE( const double mapping_determinant = mapping_jacobian.Det(); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::FaceMappingContext context; - for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *transformation = - fixture.mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *transformation = fixture.mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(transformation != nullptr); REQUIRE(transformation->Elem1 != nullptr); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetAllIntPoints(&integration_point); mfem::Vector raw_normal(dimension); @@ -1243,17 +1029,13 @@ TEST_CASE( expected_reference_normal /= raw_normal_magnitude; expected_physical_normal /= mapped_normal_magnitude; - const double expected_reference_weight = - integration_point.weight * raw_normal_magnitude; - const double expected_physical_weight = - integration_point.weight * mapped_normal_magnitude; - const double expected_normal_scale = - mapped_normal_magnitude / raw_normal_magnitude; + const double expected_reference_weight = integration_point.weight * raw_normal_magnitude; + const double expected_physical_weight = integration_point.weight * mapped_normal_magnitude; + const double expected_normal_scale = mapped_normal_magnitude / raw_normal_magnitude; REQUIRE( mapper.EvaluateFace( - element_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + element_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, context ) == mapping::MappingStatus::valid ); @@ -1262,22 +1044,10 @@ TEST_CASE( check_matrix(context.mapping.mapping_jacobian, mapping_jacobian); check_vector(context.reference_normal, expected_reference_normal); check_vector(context.quadrature.normal, expected_physical_normal); - CHECK_THAT( - context.reference_surface_weight, - WithinAbs(expected_reference_weight, tolerance) - ); - CHECK_THAT( - context.physical_surface_weight, - WithinAbs(expected_physical_weight, tolerance) - ); - CHECK_THAT( - context.quadrature.ds, - WithinAbs(expected_reference_weight, tolerance) - ); - CHECK_THAT( - context.quadrature.v_dot_n_scale, - WithinAbs(expected_normal_scale, tolerance) - ); + CHECK_THAT(context.reference_surface_weight, WithinAbs(expected_reference_weight, tolerance)); + CHECK_THAT(context.physical_surface_weight, WithinAbs(expected_physical_weight, tolerance)); + CHECK_THAT(context.quadrature.ds, WithinAbs(expected_reference_weight, tolerance)); + CHECK_THAT(context.quadrature.v_dot_n_scale, WithinAbs(expected_normal_scale, tolerance)); } } } @@ -1288,11 +1058,10 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix gradient_a = make_affine_displacement_gradient(); + const mfem::DenseMatrix gradient_a = make_affine_displacement_gradient(); mfem::DenseMatrix gradient_b(3); gradient_b = 0.0; gradient_b(0, 0) = -0.06; @@ -1305,52 +1074,38 @@ TEST_CASE( const mfem::Vector offset_a = make_vector(0.07, -0.04, 0.03); const mfem::Vector offset_b = make_vector(-0.05, 0.08, -0.02); - const mfem::Vector dofs_a = make_affine_element_dofs( - element, *transformation, gradient_a, offset_a, mfem::Ordering::byVDIM - ); - const mfem::Vector dofs_b = make_affine_element_dofs( - element, *transformation, gradient_b, offset_b, mfem::Ordering::byVDIM - ); + const mfem::Vector dofs_a = + make_affine_element_dofs(element, *transformation, gradient_a, offset_a, mfem::Ordering::byVDIM); + const mfem::Vector dofs_b = + make_affine_element_dofs(element, *transformation, gradient_b, offset_b, mfem::Ordering::byVDIM); - const mapping::ElementDisplacementData displacement_a( - element, dofs_a, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement_b( - element, dofs_b, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData displacement_a(element, dofs_a, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData displacement_b(element, dofs_b, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data_a(displacement_a); const ElementMappingDataOwner element_data_b(displacement_b); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - const mfem::IntegrationPoint &integration_point = - mfem::Geometries.GetCenter(transformation->GetGeometryType()); + const mfem::IntegrationPoint &integration_point = mfem::Geometries.GetCenter(transformation->GetGeometryType()); mapping::MappingPointContext first_a; mapping::MappingPointContext result_b; mapping::MappingPointContext second_a; REQUIRE( - mapper.EvaluatePoint( - element_data_a.Get(), *transformation, integration_point, workspace, - first_a - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data_a.Get(), *transformation, integration_point, workspace, first_a) == + mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluatePoint( - element_data_b.Get(), *transformation, integration_point, workspace, - result_b - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data_b.Get(), *transformation, integration_point, workspace, result_b) == + mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluatePoint( - element_data_a.Get(), *transformation, integration_point, workspace, - second_a - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data_a.Get(), *transformation, integration_point, workspace, second_a) == + mapping::MappingStatus::valid ); check_point_context(first_a, second_a, 0.0); @@ -1366,31 +1121,24 @@ TEST_CASE( ) { SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); - const mfem::IntegrationPoint &integration_point = - mfem::Geometries.GetCenter(transformation->GetGeometryType()); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); + const mfem::IntegrationPoint &integration_point = mfem::Geometries.GetCenter(transformation->GetGeometryType()); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::MappingPointContext context; mfem::Vector non_finite_dofs = fixture.MakeZeroElementDofs(); non_finite_dofs(0) = std::numeric_limits::quiet_NaN(); - const mapping::ElementDisplacementData non_finite_displacement( - element, non_finite_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData non_finite_displacement(element, non_finite_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner non_finite_data(non_finite_displacement); CHECK( - mapper.EvaluatePoint( - non_finite_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::non_finite_input + mapper.EvaluatePoint(non_finite_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::non_finite_input ); mfem::DenseMatrix singular_gradient(dimension); @@ -1399,20 +1147,14 @@ TEST_CASE( singular_gradient(i, i) = -1.0; const mfem::Vector zero_offset(dimension); - const mfem::Vector singular_dofs = make_affine_element_dofs( - element, *transformation, singular_gradient, zero_offset, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData singular_displacement( - element, singular_dofs, mfem::Ordering::byVDIM - ); + const mfem::Vector singular_dofs = + make_affine_element_dofs(element, *transformation, singular_gradient, zero_offset, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData singular_displacement(element, singular_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner singular_data(singular_displacement); CHECK( - mapper.EvaluatePoint( - singular_data.Get(), *transformation, integration_point, workspace, - context - ) == mapping::MappingStatus::non_positive_determinant + mapper.EvaluatePoint(singular_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::non_positive_determinant ); mfem::DenseMatrix inverted_gradient(dimension); @@ -1420,28 +1162,19 @@ TEST_CASE( for (int i = 0; i < dimension; ++i) inverted_gradient(i, i) = -2.0; - const mfem::Vector inverted_dofs = make_affine_element_dofs( - element, *transformation, inverted_gradient, zero_offset, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData inverted_displacement( - element, inverted_dofs, mfem::Ordering::byVDIM - ); + const mfem::Vector inverted_dofs = + make_affine_element_dofs(element, *transformation, inverted_gradient, zero_offset, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData inverted_displacement(element, inverted_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner inverted_data(inverted_displacement); CHECK( - mapper.EvaluatePoint( - inverted_data.Get(), *transformation, integration_point, workspace, - context - ) == mapping::MappingStatus::non_positive_determinant + mapper.EvaluatePoint(inverted_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::non_positive_determinant ); mapping::DomainMapperStateless::Workspace wrong_workspace(2); CHECK_THROWS_AS( - mapper.EvaluatePoint( - singular_data.Get(), *transformation, integration_point, - wrong_workspace, context - ), + mapper.EvaluatePoint(singular_data.Get(), *transformation, integration_point, wrong_workspace, context), std::invalid_argument ); @@ -1450,15 +1183,10 @@ TEST_CASE( const mapping::ElementDisplacementData two_dimensional_displacement( element, two_dimensional_dofs, mfem::Ordering::byVDIM ); - const ElementMappingDataOwner two_dimensional_data( - two_dimensional_displacement - ); + const ElementMappingDataOwner two_dimensional_data(two_dimensional_displacement); CHECK_THROWS_AS( - mapper.EvaluatePoint( - two_dimensional_data.Get(), *transformation, integration_point, - workspace, context - ), + mapper.EvaluatePoint(two_dimensional_data.Get(), *transformation, integration_point, workspace, context), std::invalid_argument ); } @@ -1469,46 +1197,35 @@ TEST_CASE( ) { QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); - const mfem::Vector element_dofs = make_function_element_dofs( - element, *transformation, evaluate_quadratic_displacement, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, element_dofs, mfem::Ordering::byVDIM - ); + const mfem::Vector element_dofs = + make_function_element_dofs(element, *transformation, evaluate_quadratic_displacement, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData displacement(element, element_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); mapping::MappingPointContext context; - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mfem::Vector reference_position(dimension); mfem::Vector expected_displacement(dimension); transformation->Transform(integration_point, reference_position); - evaluate_quadratic_displacement( - reference_position, expected_displacement - ); + evaluate_quadratic_displacement(reference_position, expected_displacement); mfem::Vector expected_position(reference_position); expected_position += expected_displacement; - const mfem::DenseMatrix displacement_gradient = - evaluate_quadratic_displacement_gradient(reference_position); - mfem::DenseMatrix expected_jacobian = make_identity_matrix(dimension); + const mfem::DenseMatrix displacement_gradient = evaluate_quadratic_displacement_gradient(reference_position); + mfem::DenseMatrix expected_jacobian = make_identity_matrix(dimension); expected_jacobian.Add(1.0, displacement_gradient); mfem::DenseMatrix expected_inverse(dimension); @@ -1517,38 +1234,19 @@ TEST_CASE( REQUIRE(expected_determinant > 0.0); REQUIRE( - mapper.EvaluatePoint( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); CAPTURE(q); REQUIRE_FALSE(context.compactified); - check_vector( - context.reference_position, reference_position, polynomial_tolerance - ); - check_vector( - context.displaced_position, expected_position, polynomial_tolerance - ); - check_vector( - context.physical_position, expected_position, polynomial_tolerance - ); - check_matrix( - context.displacement_jacobian, expected_jacobian, - polynomial_tolerance - ); - check_matrix( - context.mapping_jacobian, expected_jacobian, polynomial_tolerance - ); - check_matrix( - context.inverse_mapping_jacobian, expected_inverse, - polynomial_tolerance - ); - CHECK_THAT( - context.mapping_determinant, - WithinAbs(expected_determinant, polynomial_tolerance) - ); + check_vector(context.reference_position, reference_position, polynomial_tolerance); + check_vector(context.displaced_position, expected_position, polynomial_tolerance); + check_vector(context.physical_position, expected_position, polynomial_tolerance); + check_matrix(context.displacement_jacobian, expected_jacobian, polynomial_tolerance); + check_matrix(context.mapping_jacobian, expected_jacobian, polynomial_tolerance); + check_matrix(context.inverse_mapping_jacobian, expected_inverse, polynomial_tolerance); + CHECK_THAT(context.mapping_determinant, WithinAbs(expected_determinant, polynomial_tolerance)); } } @@ -1560,39 +1258,27 @@ TEST_CASE( QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); const QuadraticMappingData mapping_data(element, *transformation); - const mapping::ElementDisplacementData base_displacement( - element, mapping_data.base_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData direction( - element, mapping_data.direction_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData plus_displacement( - element, mapping_data.plus_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData minus_displacement( - element, mapping_data.minus_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData base_displacement(element, mapping_data.base_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData direction(element, mapping_data.direction_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData plus_displacement(element, mapping_data.plus_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData minus_displacement(element, mapping_data.minus_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner base_data(base_displacement); const ElementMappingDataOwner plus_data(plus_displacement); const ElementMappingDataOwner minus_data(minus_displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::MappingPointContext base_context; mapping::MappingPointContext plus_context; @@ -1600,28 +1286,21 @@ TEST_CASE( mapping::MappingPointVariation variation; REQUIRE( - mapper.EvaluatePoint( - base_data.Get(), *transformation, integration_point, workspace, - base_context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(base_data.Get(), *transformation, integration_point, workspace, base_context) == + mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluatePointVariation( - base_data.Get(), direction, *transformation, integration_point, - base_context, workspace, variation + base_data.Get(), direction, *transformation, integration_point, base_context, workspace, variation ) == mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluatePoint( - plus_data.Get(), *transformation, integration_point, workspace, - plus_context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(plus_data.Get(), *transformation, integration_point, workspace, plus_context) == + mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluatePoint( - minus_data.Get(), *transformation, integration_point, workspace, - minus_context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(minus_data.Get(), *transformation, integration_point, workspace, minus_context) == + mapping::MappingStatus::valid ); mfem::Vector reference_position(dimension); @@ -1629,53 +1308,31 @@ TEST_CASE( transformation->Transform(integration_point, reference_position); evaluate_quadratic_direction(reference_position, expected_direction); - const mfem::DenseMatrix expected_direction_gradient = - evaluate_quadratic_direction_gradient(reference_position); + const mfem::DenseMatrix expected_direction_gradient = evaluate_quadratic_direction_gradient(reference_position); CAPTURE(q); - check_vector( - variation.displacement_variation, expected_direction, - polynomial_tolerance - ); - check_vector( - variation.physical_position_variation, expected_direction, - polynomial_tolerance - ); - check_matrix( - variation.displacement_jacobian_variation, - expected_direction_gradient, polynomial_tolerance - ); - check_matrix( - variation.mapping_jacobian_variation, expected_direction_gradient, - polynomial_tolerance - ); + check_vector(variation.displacement_variation, expected_direction, polynomial_tolerance); + check_vector(variation.physical_position_variation, expected_direction, polynomial_tolerance); + check_matrix(variation.displacement_jacobian_variation, expected_direction_gradient, polynomial_tolerance); + check_matrix(variation.mapping_jacobian_variation, expected_direction_gradient, polynomial_tolerance); check_vector_central_difference( - plus_context.physical_position, minus_context.physical_position, - variation.physical_position_variation, difference_step, - linearization_tolerance + plus_context.physical_position, minus_context.physical_position, variation.physical_position_variation, + difference_step, linearization_tolerance ); check_matrix_central_difference( - plus_context.mapping_jacobian, minus_context.mapping_jacobian, - variation.mapping_jacobian_variation, difference_step, - linearization_tolerance + plus_context.mapping_jacobian, minus_context.mapping_jacobian, variation.mapping_jacobian_variation, + difference_step, linearization_tolerance ); check_matrix_central_difference( - plus_context.inverse_mapping_jacobian, - minus_context.inverse_mapping_jacobian, - variation.inverse_mapping_jacobian_variation, difference_step, - linearization_tolerance + plus_context.inverse_mapping_jacobian, minus_context.inverse_mapping_jacobian, + variation.inverse_mapping_jacobian_variation, difference_step, linearization_tolerance ); const double determinant_finite_difference = - (plus_context.mapping_determinant - - minus_context.mapping_determinant) / - (2.0 * difference_step); + (plus_context.mapping_determinant - minus_context.mapping_determinant) / (2.0 * difference_step); CHECK_THAT( - determinant_finite_difference, - WithinAbs( - variation.mapping_determinant_variation, linearization_tolerance - ) + determinant_finite_difference, WithinAbs(variation.mapping_determinant_variation, linearization_tolerance) ); } } @@ -1688,39 +1345,27 @@ TEST_CASE( QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); const QuadraticMappingData mapping_data(element, *transformation); - const mapping::ElementDisplacementData base_displacement( - element, mapping_data.base_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData direction( - element, mapping_data.direction_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData plus_displacement( - element, mapping_data.plus_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData minus_displacement( - element, mapping_data.minus_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData base_displacement(element, mapping_data.base_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData direction(element, mapping_data.direction_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData plus_displacement(element, mapping_data.plus_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData minus_displacement(element, mapping_data.minus_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner base_data(base_displacement); const ElementMappingDataOwner plus_data(plus_displacement); const ElementMappingDataOwner minus_data(minus_displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::VolumeMappingContext base_context; mapping::VolumeMappingContext plus_context; @@ -1728,55 +1373,39 @@ TEST_CASE( mapping::VolumeMappingVariation variation; REQUIRE( - mapper.EvaluateVolume( - base_data.Get(), *transformation, integration_point, workspace, - base_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(base_data.Get(), *transformation, integration_point, workspace, base_context) == + mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateVolumeVariation( - base_data.Get(), direction, *transformation, integration_point, - base_context, workspace, variation + base_data.Get(), direction, *transformation, integration_point, base_context, workspace, variation ) == mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluateVolume( - plus_data.Get(), *transformation, integration_point, workspace, - plus_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(plus_data.Get(), *transformation, integration_point, workspace, plus_context) == + mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluateVolume( - minus_data.Get(), *transformation, integration_point, workspace, - minus_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(minus_data.Get(), *transformation, integration_point, workspace, minus_context) == + mapping::MappingStatus::valid ); CAPTURE(q); check_matrix_central_difference( - plus_context.quadrature.J_inv, minus_context.quadrature.J_inv, - variation.inverse_element_jacobian_variation, difference_step, - linearization_tolerance + plus_context.quadrature.J_inv, minus_context.quadrature.J_inv, variation.inverse_element_jacobian_variation, + difference_step, linearization_tolerance ); const double determinant_finite_difference = - (plus_context.quadrature.detJ - minus_context.quadrature.detJ) / - (2.0 * difference_step); + (plus_context.quadrature.detJ - minus_context.quadrature.detJ) / (2.0 * difference_step); const double weight_finite_difference = - (plus_context.quadrature.weight - minus_context.quadrature.weight) / - (2.0 * difference_step); + (plus_context.quadrature.weight - minus_context.quadrature.weight) / (2.0 * difference_step); CHECK_THAT( determinant_finite_difference, - WithinAbs( - variation.mapping.mapping_determinant_variation, - linearization_tolerance - ) - ); - CHECK_THAT( - weight_finite_difference, - WithinAbs(variation.weight_variation, linearization_tolerance) + WithinAbs(variation.mapping.mapping_determinant_variation, linearization_tolerance) ); + CHECK_THAT(weight_finite_difference, WithinAbs(variation.weight_variation, linearization_tolerance)); } } @@ -1789,46 +1418,32 @@ TEST_CASE( QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *element_transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *element_transformation = fixture.mesh.GetElementTransformation(0); const QuadraticMappingData mapping_data(element, *element_transformation); - const mapping::ElementDisplacementData base_displacement( - element, mapping_data.base_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData direction( - element, mapping_data.direction_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData plus_displacement( - element, mapping_data.plus_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData minus_displacement( - element, mapping_data.minus_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData base_displacement(element, mapping_data.base_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData direction(element, mapping_data.direction_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData plus_displacement(element, mapping_data.plus_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData minus_displacement(element, mapping_data.minus_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner base_data(base_displacement); const ElementMappingDataOwner plus_data(plus_displacement); const ElementMappingDataOwner minus_data(minus_displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *transformation = - fixture.mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *transformation = fixture.mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(transformation != nullptr); REQUIRE(transformation->Elem1 != nullptr); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 4); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 4); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::FaceMappingContext base_context; mapping::FaceMappingContext plus_context; @@ -1837,61 +1452,48 @@ TEST_CASE( REQUIRE( mapper.EvaluateFace( - base_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, - workspace, base_context + base_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, + base_context ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFaceVariation( - base_data.Get(), direction, *transformation, - mapping::FaceElementSide::element_1, integration_point, + base_data.Get(), direction, *transformation, mapping::FaceElementSide::element_1, integration_point, base_context, workspace, variation ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFace( - plus_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, - workspace, plus_context + plus_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, + plus_context ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFace( - minus_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + minus_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, minus_context ) == mapping::MappingStatus::valid ); CAPTURE(boundary_element, q); check_vector_central_difference( - plus_context.quadrature.normal, minus_context.quadrature.normal, - variation.physical_normal_variation, difference_step, - normal_tolerance + plus_context.quadrature.normal, minus_context.quadrature.normal, variation.physical_normal_variation, + difference_step, normal_tolerance ); const double surface_weight_finite_difference = - (plus_context.physical_surface_weight - - minus_context.physical_surface_weight) / + (plus_context.physical_surface_weight - minus_context.physical_surface_weight) / (2.0 * difference_step); const double normal_scale_finite_difference = - (plus_context.quadrature.v_dot_n_scale - - minus_context.quadrature.v_dot_n_scale) / + (plus_context.quadrature.v_dot_n_scale - minus_context.quadrature.v_dot_n_scale) / (2.0 * difference_step); CHECK_THAT( surface_weight_finite_difference, - WithinAbs( - variation.physical_surface_weight_variation, - measure_tolerance - ) + WithinAbs(variation.physical_surface_weight_variation, measure_tolerance) ); CHECK_THAT( - normal_scale_finite_difference, - WithinAbs( - variation.normal_flux_scale_variation, measure_tolerance - ) + normal_scale_finite_difference, WithinAbs(variation.normal_flux_scale_variation, measure_tolerance) ); } } @@ -1903,16 +1505,12 @@ TEST_CASE( ) { QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *transformation = - fixture.mesh.GetElementTransformation(0); - const mfem::IntegrationPoint &integration_point = - mfem::Geometries.GetCenter(transformation->GetGeometryType()); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *transformation = fixture.mesh.GetElementTransformation(0); + const mfem::IntegrationPoint &integration_point = mfem::Geometries.GetCenter(transformation->GetGeometryType()); const QuadraticMappingData mapping_data(element, *transformation); - const mapping::ElementDisplacementData base_displacement( - element, mapping_data.base_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData base_displacement(element, mapping_data.base_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner base_data(base_displacement); mfem::Vector non_finite_direction_dofs(mapping_data.direction_dofs); @@ -1922,8 +1520,7 @@ TEST_CASE( ); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); @@ -1931,30 +1528,27 @@ TEST_CASE( mapping::MappingPointVariation variation; REQUIRE( - mapper.EvaluatePoint( - base_data.Get(), *transformation, integration_point, workspace, - base_context - ) == mapping::MappingStatus::valid + mapper.EvaluatePoint(base_data.Get(), *transformation, integration_point, workspace, base_context) == + mapping::MappingStatus::valid ); CHECK( mapper.EvaluatePointVariation( - base_data.Get(), non_finite_direction, *transformation, - integration_point, base_context, workspace, variation + base_data.Get(), non_finite_direction, *transformation, integration_point, base_context, workspace, + variation ) == mapping::MappingStatus::non_finite_input ); SingleElementFixture linear_fixture; const mfem::FiniteElement &linear_element = linear_fixture.GetElement(); - const mfem::Vector linear_direction_dofs = - linear_fixture.MakeZeroElementDofs(); + const mfem::Vector linear_direction_dofs = linear_fixture.MakeZeroElementDofs(); const mapping::ElementDisplacementData incompatible_direction( linear_element, linear_direction_dofs, mfem::Ordering::byVDIM ); CHECK_THROWS_AS( mapper.EvaluatePointVariation( - base_data.Get(), incompatible_direction, *transformation, - integration_point, base_context, workspace, variation + base_data.Get(), incompatible_direction, *transformation, integration_point, base_context, workspace, + variation ), std::invalid_argument ); @@ -1982,9 +1576,7 @@ TEST_CASE( REQUIRE(context.mapping_determinant > 0.0); context.inverse_mapping_jacobian.SetSize(3); - mfem::CalcInverse( - context.mapping_jacobian, context.inverse_mapping_jacobian - ); + mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian); const mfem::Vector reference_flux = make_vector(0.7, -0.4, 1.1); const mfem::Vector reference_test_flux = make_vector(-0.2, 0.9, 0.5); @@ -1999,22 +1591,12 @@ TEST_CASE( mfem::Vector physical_test_gradient; mapping::MapHDivFluxToPhysical(context, reference_flux, physical_flux); - mapping::MapPhysicalFluxToHDivReference( - context, physical_flux, recovered_flux - ); - mapping::MapHDivFluxToPhysical( - context, reference_test_flux, physical_test_flux - ); + mapping::MapPhysicalFluxToHDivReference(context, physical_flux, recovered_flux); + mapping::MapHDivFluxToPhysical(context, reference_test_flux, physical_test_flux); - mapping::MapReferenceGradientToPhysical( - context, reference_gradient, physical_gradient - ); - mapping::MapPhysicalGradientToReference( - context, physical_gradient, recovered_gradient - ); - mapping::MapReferenceGradientToPhysical( - context, reference_test_gradient, physical_test_gradient - ); + mapping::MapReferenceGradientToPhysical(context, reference_gradient, physical_gradient); + mapping::MapPhysicalGradientToReference(context, physical_gradient, recovered_gradient); + mapping::MapReferenceGradientToPhysical(context, reference_test_gradient, physical_test_gradient); check_vector(recovered_flux, reference_flux, transform_tolerance); check_vector(recovered_gradient, reference_gradient, transform_tolerance); @@ -2033,16 +1615,9 @@ TEST_CASE( mfem::DenseMatrix physical_vector_gradient; mfem::DenseMatrix recovered_vector_gradient; - mapping::MapReferenceVectorGradientToPhysical( - context, reference_vector_gradient, physical_vector_gradient - ); - mapping::MapPhysicalVectorGradientToReference( - context, physical_vector_gradient, recovered_vector_gradient - ); - check_matrix( - recovered_vector_gradient, reference_vector_gradient, - transform_tolerance - ); + mapping::MapReferenceVectorGradientToPhysical(context, reference_vector_gradient, physical_vector_gradient); + mapping::MapPhysicalVectorGradientToReference(context, physical_vector_gradient, recovered_vector_gradient); + check_matrix(recovered_vector_gradient, reference_vector_gradient, transform_tolerance); mfem::DenseMatrix hdiv_mass_tensor; mfem::DenseMatrix diffusion_tensor; @@ -2054,34 +1629,18 @@ TEST_CASE( hdiv_mass_tensor.Mult(reference_test_flux, mass_action); diffusion_tensor.Mult(reference_test_gradient, diffusion_action); - const double physical_hdiv_inner_product = - context.mapping_determinant * (physical_flux * physical_test_flux); + const double physical_hdiv_inner_product = context.mapping_determinant * (physical_flux * physical_test_flux); const double reference_hdiv_inner_product = reference_flux * mass_action; const double physical_gradient_inner_product = - context.mapping_determinant * - (physical_gradient * physical_test_gradient); - const double reference_gradient_inner_product = - reference_gradient * diffusion_action; + context.mapping_determinant * (physical_gradient * physical_test_gradient); + const double reference_gradient_inner_product = reference_gradient * diffusion_action; - CHECK_THAT( - physical_hdiv_inner_product, - WithinAbs(reference_hdiv_inner_product, transform_tolerance) - ); - CHECK_THAT( - physical_gradient_inner_product, - WithinAbs(reference_gradient_inner_product, transform_tolerance) - ); + CHECK_THAT(physical_hdiv_inner_product, WithinAbs(reference_hdiv_inner_product, transform_tolerance)); + CHECK_THAT(physical_gradient_inner_product, WithinAbs(reference_gradient_inner_product, transform_tolerance)); const double reference_divergence = 0.73; - const double physical_divergence = - mapping::MapHDivDivergenceToPhysical(context, reference_divergence); - CHECK_THAT( - physical_divergence, - WithinAbs( - reference_divergence / context.mapping_determinant, - transform_tolerance - ) - ); + const double physical_divergence = mapping::MapHDivDivergenceToPhysical(context, reference_divergence); + CHECK_THAT(physical_divergence, WithinAbs(reference_divergence / context.mapping_determinant, transform_tolerance)); } TEST_CASE( @@ -2092,69 +1651,51 @@ TEST_CASE( SingleElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *element_transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *element_transformation = fixture.mesh.GetElementTransformation(0); - const mfem::DenseMatrix displacement_gradient = - make_affine_displacement_gradient(); - const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); - const mfem::Vector element_dofs = make_affine_element_dofs( - element, *element_transformation, displacement_gradient, - displacement_offset, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, element_dofs, mfem::Ordering::byVDIM + const mfem::DenseMatrix displacement_gradient = make_affine_displacement_gradient(); + const mfem::Vector displacement_offset = make_vector(0.07, -0.04, 0.03); + const mfem::Vector element_dofs = make_affine_element_dofs( + element, *element_transformation, displacement_gradient, displacement_offset, mfem::Ordering::byVDIM ); + const mapping::ElementDisplacementData displacement(element, element_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); const mfem::Vector reference_flux = make_vector(0.7, -0.4, 1.1); - for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *transformation = - fixture.mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *transformation = fixture.mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(transformation != nullptr); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::FaceMappingContext context; mfem::Vector physical_flux; REQUIRE( mapper.EvaluateFace( - element_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + element_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, context ) == mapping::MappingStatus::valid ); - mapping::MapHDivFluxToPhysical( - context.mapping, reference_flux, physical_flux - ); + mapping::MapHDivFluxToPhysical(context.mapping, reference_flux, physical_flux); const double reference_integrated_flux = - (reference_flux * context.reference_normal) * - context.reference_surface_weight; + (reference_flux * context.reference_normal) * context.reference_surface_weight; const double physical_integrated_flux = - (physical_flux * context.quadrature.normal) * - context.physical_surface_weight; + (physical_flux * context.quadrature.normal) * context.physical_surface_weight; CAPTURE(boundary_element, q); - CHECK_THAT( - physical_integrated_flux, - WithinAbs(reference_integrated_flux, flux_tolerance) - ); + CHECK_THAT(physical_integrated_flux, WithinAbs(reference_integrated_flux, flux_tolerance)); } } } @@ -2175,82 +1716,68 @@ TEST_CASE( mesh_config.r_star = r_star; mesh_config.r_infinity = r_infinity; mesh_config.flattening = 0.0; - mesh_config.optimization_methods = - stroid::config::OptimizationMethods{false, true}; + mesh_config.optimization_methods = stroid::config::OptimizationMethods{false, true}; - stroid::StroidMesh stroid_mesh = stroid::GenerateMesh(mesh_config); - mfem::Mesh &mesh = *stroid_mesh.mesh; + stroid::StroidMesh stroid_mesh = stroid::GenerateMesh(mesh_config); + mfem::Mesh &mesh = *stroid_mesh.mesh; REQUIRE(stroid_mesh.exterior_coordinate != nullptr); REQUIRE(stroid_mesh.exterior_coordinate->space != nullptr); REQUIRE(stroid_mesh.exterior_coordinate->values != nullptr); - mfem::FiniteElementSpace &compactification_space = - *stroid_mesh.exterior_coordinate->space; - mfem::GridFunction &compactification_coordinate = - *stroid_mesh.exterior_coordinate->values; + mfem::FiniteElementSpace &compactification_space = *stroid_mesh.exterior_coordinate->space; + mfem::GridFunction &compactification_coordinate = *stroid_mesh.exterior_coordinate->values; mfem::H1_FECollection displacement_collection(3, dimension); - mfem::FiniteElementSpace displacement_space( - &mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_space(&mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_space); - auto rotating_displacement = [r_star, r_infinity]( - const mfem::Vector &reference_position, - mfem::Vector &displacement_value - ) { - const double x = reference_position(0); - const double y = reference_position(1); - const double z = reference_position(2); - const double radius_squared = x * x + y * y + z * z; - const double radius = std::sqrt(radius_squared); + auto rotating_displacement = + [r_star, r_infinity](const mfem::Vector &reference_position, mfem::Vector &displacement_value) { + const double x = reference_position(0); + const double y = reference_position(1); + const double z = reference_position(2); + const double radius_squared = x * x + y * y + z * z; + const double radius = std::sqrt(radius_squared); - displacement_value.SetSize(3); - displacement_value = 0.0; + displacement_value.SetSize(3); + displacement_value = 0.0; - if (radius <= 1.0e-14) - return; + if (radius <= 1.0e-14) + return; - const double cylindrical_fraction = (x * x + y * y) / radius_squared; - const double angular_deformation = - 0.20 * cylindrical_fraction + - 0.12 * cylindrical_fraction * cylindrical_fraction; + const double cylindrical_fraction = (x * x + y * y) / radius_squared; + const double angular_deformation = + 0.20 * cylindrical_fraction + 0.12 * cylindrical_fraction * cylindrical_fraction; - double radial_extension = 0.0; - if (radius <= r_star) { - radial_extension = radius_squared / (r_star * r_star); - } else { - radial_extension = - std::max(0.0, (r_infinity - radius) / (r_infinity - r_star)); - } + double radial_extension = 0.0; + if (radius <= r_star) { + radial_extension = radius_squared / (r_star * r_star); + } else { + radial_extension = std::max(0.0, (r_infinity - radius) / (r_infinity - r_star)); + } - const double scale = radial_extension * angular_deformation; - displacement_value(0) = scale * x; - displacement_value(1) = scale * y; - displacement_value(2) = scale * z; - }; + const double scale = radial_extension * angular_deformation; + displacement_value(0) = scale * x; + displacement_value(1) = scale * y; + displacement_value(2) = scale * z; + }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dimension, rotating_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dimension, rotating_displacement); displacement.ProjectCoefficient(displacement_coefficient); - std::unique_ptr - exterior_map = std::make_unique< - mapping::compactification::KelvinCompactification>( + std::unique_ptr exterior_map = + std::make_unique( mapping::compactification::options::KelvinCompactificationOptions{ .r_star_ref = r_star, .r_inf_ref = r_infinity } ); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - std::move(exterior_map) + {.dimension = dimension, .vacuum_element_attribute = 3}, std::move(exterior_map) ); mapping::DomainMapperStateless::Workspace workspace(dimension); - double minimum_mapping_determinant = - std::numeric_limits::infinity(); + double minimum_mapping_determinant = std::numeric_limits::infinity(); double maximum_mapping_determinant = 0.0; double stellar_volume = 0.0; double moment_x = 0.0; @@ -2261,10 +1788,8 @@ TEST_CASE( int vacuum_elements = 0; for (int element_id = 0; element_id < mesh.GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(element_id); - const mfem::FiniteElement *element = - displacement_space.GetFE(element_id); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(element_id); + const mfem::FiniteElement *element = displacement_space.GetFE(element_id); mfem::Array element_vdofs; mfem::Vector element_dofs; @@ -2272,27 +1797,19 @@ TEST_CASE( displacement.GetSubVector(element_vdofs, element_dofs); const mapping::ElementDisplacementData element_displacement = - mapping::ElementDisplacementDataFromElementVDofs( - *element, element_dofs - ); + mapping::ElementDisplacementDataFromElementVDofs(*element, element_dofs); mfem::Array compactification_dof_indices; mfem::Vector compactification_dofs; - compactification_space.GetElementDofs( - element_id, compactification_dof_indices - ); - compactification_coordinate.GetSubVector( - compactification_dof_indices, compactification_dofs - ); + compactification_space.GetElementDofs(element_id, compactification_dof_indices); + compactification_coordinate.GetSubVector(compactification_dof_indices, compactification_dofs); const ElementMappingDataOwner element_data( - element_displacement, *compactification_space.GetFE(element_id), - compactification_dofs + element_displacement, *compactification_space.GetFE(element_id), compactification_dofs ); const int quadrature_order = 2 * element->GetOrder() + 6; - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); if (transformation->Attribute == 3) { ++vacuum_elements; @@ -2301,95 +1818,60 @@ TEST_CASE( } for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::VolumeMappingContext context; REQUIRE( - mapper.EvaluateVolume( - element_data.Get(), *transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(element_data.Get(), *transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); - minimum_mapping_determinant = std::min( - minimum_mapping_determinant, context.mapping.mapping_determinant - ); - maximum_mapping_determinant = std::max( - maximum_mapping_determinant, context.mapping.mapping_determinant - ); + minimum_mapping_determinant = std::min(minimum_mapping_determinant, context.mapping.mapping_determinant); + maximum_mapping_determinant = std::max(maximum_mapping_determinant, context.mapping.mapping_determinant); REQUIRE(context.mapping.mapping_determinant > 0.0); REQUIRE(context.quadrature.weight > 0.0); mfem::Vector reference_flux = make_vector( - 0.4 + context.mapping.reference_position(0), - -0.3 + 0.5 * context.mapping.reference_position(1), + 0.4 + context.mapping.reference_position(0), -0.3 + 0.5 * context.mapping.reference_position(1), 0.7 - 0.2 * context.mapping.reference_position(2) ); mfem::Vector physical_flux; mfem::Vector recovered_flux; - mapping::MapHDivFluxToPhysical( - context.mapping, reference_flux, physical_flux - ); - mapping::MapPhysicalFluxToHDivReference( - context.mapping, physical_flux, recovered_flux - ); + mapping::MapHDivFluxToPhysical(context.mapping, reference_flux, physical_flux); + mapping::MapPhysicalFluxToHDivReference(context.mapping, physical_flux, recovered_flux); check_vector(recovered_flux, reference_flux, transform_tolerance); const mfem::Vector reference_gradient = make_vector(0.3, -0.5, 0.8); mfem::Vector physical_gradient; mfem::Vector recovered_gradient; - mapping::MapReferenceGradientToPhysical( - context.mapping, reference_gradient, physical_gradient - ); - mapping::MapPhysicalGradientToReference( - context.mapping, physical_gradient, recovered_gradient - ); - check_vector( - recovered_gradient, reference_gradient, transform_tolerance - ); + mapping::MapReferenceGradientToPhysical(context.mapping, reference_gradient, physical_gradient); + mapping::MapPhysicalGradientToReference(context.mapping, physical_gradient, recovered_gradient); + check_vector(recovered_gradient, reference_gradient, transform_tolerance); if (transformation->Attribute == 3) { transformation->SetIntPoint(&integration_point); mfem::Vector compactification_gradient(dimension); - const double coordinate = compactification_coordinate.GetValue( - element_id, integration_point - ); - compactification_coordinate.GetGradient( - *transformation, compactification_gradient - ); + const double coordinate = compactification_coordinate.GetValue(element_id, integration_point); + compactification_coordinate.GetGradient(*transformation, compactification_gradient); mapping::compactification::ExteriorMapResult direct_result; const mapping::compactification::ExteriorMapInput direct_input{ - .reference_position = context.mapping.reference_position, - .displaced_position = context.mapping.displaced_position, - .displacement_jacobian = - context.mapping.displacement_jacobian, - .compactification_coordinate = coordinate, - .compactification_coordinate_gradient = - compactification_gradient + .reference_position = context.mapping.reference_position, + .displaced_position = context.mapping.displaced_position, + .displacement_jacobian = context.mapping.displacement_jacobian, + .compactification_coordinate = coordinate, + .compactification_coordinate_gradient = compactification_gradient }; - REQUIRE( - mapper.GetExteriorMap().Evaluate( - direct_input, direct_result - ) == mapping::MappingStatus::valid - ); - check_vector( - context.mapping.physical_position, - direct_result.physical_position, transform_tolerance - ); - check_matrix( - context.mapping.mapping_jacobian, - direct_result.mapping_jacobian, transform_tolerance - ); + REQUIRE(mapper.GetExteriorMap().Evaluate(direct_input, direct_result) == mapping::MappingStatus::valid); + check_vector(context.mapping.physical_position, direct_result.physical_position, transform_tolerance); + check_matrix(context.mapping.mapping_jacobian, direct_result.mapping_jacobian, transform_tolerance); } else { check_vector( - context.mapping.physical_position, - context.mapping.displaced_position, transform_tolerance + context.mapping.physical_position, context.mapping.displaced_position, transform_tolerance ); const double x = context.mapping.physical_position(0); @@ -2416,13 +1898,9 @@ TEST_CASE( const double quadrupole_y = 3.0 * moment_y - moment_trace; const double quadrupole_z = 3.0 * moment_z - moment_trace; const double normalized_quadrupole = - std::sqrt( - quadrupole_x * quadrupole_x + quadrupole_y * quadrupole_y + - quadrupole_z * quadrupole_z - ) / + std::sqrt(quadrupole_x * quadrupole_x + quadrupole_y * quadrupole_y + quadrupole_z * quadrupole_z) / moment_trace; - const double axisymmetry_error = - std::abs(moment_x - moment_y) / (0.5 * (moment_x + moment_y)); + const double axisymmetry_error = std::abs(moment_x - moment_y) / (0.5 * (moment_x + moment_y)); INFO("Stellar volume = " << stellar_volume); INFO("Minimum mapping determinant = " << minimum_mapping_determinant); @@ -2443,9 +1921,7 @@ TEST_CASE( constexpr double relative_tolerance = 2.0e-6; constexpr double kelvin_difference_step = 2.0e-4; - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 1, 1, 1, mfem::Element::HEXAHEDRON, 2.98, 0.02, 0.02 - ); + mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 2.98, 0.02, 0.02); for (int vertex_id = 0; vertex_id < mesh.GetNV(); ++vertex_id) { double *vertex = mesh.GetVertex(vertex_id); @@ -2458,75 +1934,48 @@ TEST_CASE( mesh.SetAttributes(); mfem::H1_FECollection displacement_collection(2, dimension); - mfem::FiniteElementSpace displacement_space( - &mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_space(&mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM); - const mfem::FiniteElement &element = *displacement_space.GetFE(0); - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = *displacement_space.GetFE(0); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); - const mfem::Vector base_dofs = make_function_element_dofs( - element, *transformation, evaluate_quadratic_displacement, - mfem::Ordering::byVDIM - ); - const mfem::Vector direction_dofs = make_function_element_dofs( - element, *transformation, evaluate_quadratic_direction, - mfem::Ordering::byVDIM - ); + const mfem::Vector base_dofs = + make_function_element_dofs(element, *transformation, evaluate_quadratic_displacement, mfem::Ordering::byVDIM); + const mfem::Vector direction_dofs = + make_function_element_dofs(element, *transformation, evaluate_quadratic_direction, mfem::Ordering::byVDIM); mfem::Vector plus_dofs(base_dofs); mfem::Vector minus_dofs(base_dofs); plus_dofs.Add(kelvin_difference_step, direction_dofs); minus_dofs.Add(-kelvin_difference_step, direction_dofs); - const mapping::ElementDisplacementData base_displacement( - element, base_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData direction( - element, direction_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData plus_displacement( - element, plus_dofs, mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData minus_displacement( - element, minus_dofs, mfem::Ordering::byVDIM - ); + const mapping::ElementDisplacementData base_displacement(element, base_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData direction(element, direction_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData plus_displacement(element, plus_dofs, mfem::Ordering::byVDIM); + const mapping::ElementDisplacementData minus_displacement(element, minus_dofs, mfem::Ordering::byVDIM); const mfem::Vector compactification_dofs = - make_compactification_element_dofs( - element, *transformation, - [](const mfem::Vector &reference_position) { - return (reference_position(0) - 1.0) / 3.0; - } - ); + make_compactification_element_dofs(element, *transformation, [](const mfem::Vector &reference_position) { + return (reference_position(0) - 1.0) / 3.0; + }); - const ElementMappingDataOwner base_data( - base_displacement, element, compactification_dofs - ); - const ElementMappingDataOwner plus_data( - plus_displacement, element, compactification_dofs - ); - const ElementMappingDataOwner minus_data( - minus_displacement, element, compactification_dofs - ); + const ElementMappingDataOwner base_data(base_displacement, element, compactification_dofs); + const ElementMappingDataOwner plus_data(plus_displacement, element, compactification_dofs); + const ElementMappingDataOwner minus_data(minus_displacement, element, compactification_dofs); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - auto vector_central_difference = [](const mfem::Vector &plus_value, - const mfem::Vector &minus_value) { + auto vector_central_difference = [](const mfem::Vector &plus_value, const mfem::Vector &minus_value) { mfem::Vector difference(plus_value); difference -= minus_value; difference *= 1.0 / (2.0 * kelvin_difference_step); return difference; }; - auto matrix_central_difference = [](const mfem::DenseMatrix &plus_value, - const mfem::DenseMatrix &minus_value) { + auto matrix_central_difference = [](const mfem::DenseMatrix &plus_value, const mfem::DenseMatrix &minus_value) { mfem::DenseMatrix difference(plus_value); difference -= minus_value; difference *= 1.0 / (2.0 * kelvin_difference_step); @@ -2546,115 +1995,82 @@ TEST_CASE( mapping::VolumeMappingVariation variation; REQUIRE( - mapper.EvaluateVolume( - base_data.Get(), *transformation, integration_point, workspace, - base_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(base_data.Get(), *transformation, integration_point, workspace, base_context) == + mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateVolumeVariation( - base_data.Get(), direction, *transformation, integration_point, - base_context, workspace, variation + base_data.Get(), direction, *transformation, integration_point, base_context, workspace, variation ) == mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluateVolume( - plus_data.Get(), *transformation, integration_point, workspace, - plus_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(plus_data.Get(), *transformation, integration_point, workspace, plus_context) == + mapping::MappingStatus::valid ); REQUIRE( - mapper.EvaluateVolume( - minus_data.Get(), *transformation, integration_point, workspace, - minus_context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(minus_data.Get(), *transformation, integration_point, workspace, minus_context) == + mapping::MappingStatus::valid ); CAPTURE(xi); REQUIRE(base_context.mapping.compactified); const mfem::Vector physical_position_difference = - vector_central_difference( - plus_context.mapping.physical_position, - minus_context.mapping.physical_position - ); + vector_central_difference(plus_context.mapping.physical_position, minus_context.mapping.physical_position); const mfem::DenseMatrix mapping_jacobian_difference = - matrix_central_difference( - plus_context.mapping.mapping_jacobian, - minus_context.mapping.mapping_jacobian - ); - const mfem::DenseMatrix inverse_mapping_jacobian_difference = - matrix_central_difference( - plus_context.mapping.inverse_mapping_jacobian, - minus_context.mapping.inverse_mapping_jacobian - ); + matrix_central_difference(plus_context.mapping.mapping_jacobian, minus_context.mapping.mapping_jacobian); + const mfem::DenseMatrix inverse_mapping_jacobian_difference = matrix_central_difference( + plus_context.mapping.inverse_mapping_jacobian, minus_context.mapping.inverse_mapping_jacobian + ); const mfem::DenseMatrix inverse_element_jacobian_difference = - matrix_central_difference( - plus_context.quadrature.J_inv, minus_context.quadrature.J_inv - ); + matrix_central_difference(plus_context.quadrature.J_inv, minus_context.quadrature.J_inv); CHECK_THAT( - relative_vector_difference( - physical_position_difference, - variation.mapping.physical_position_variation + relative_vector_difference(physical_position_difference, variation.mapping.physical_position_variation), + Catch::Matchers::WithinAbs(0.0, relative_tolerance) + ); + CHECK_THAT( + relative_matrix_difference(mapping_jacobian_difference, variation.mapping.mapping_jacobian_variation), + Catch::Matchers::WithinAbs(0.0, relative_tolerance) + ); + CHECK_THAT( + relative_matrix_difference( + inverse_mapping_jacobian_difference, variation.mapping.inverse_mapping_jacobian_variation ), Catch::Matchers::WithinAbs(0.0, relative_tolerance) ); CHECK_THAT( relative_matrix_difference( - mapping_jacobian_difference, - variation.mapping.mapping_jacobian_variation - ), - Catch::Matchers::WithinAbs(0.0, relative_tolerance) - ); - CHECK_THAT( - relative_matrix_difference( - inverse_mapping_jacobian_difference, - variation.mapping.inverse_mapping_jacobian_variation - ), - Catch::Matchers::WithinAbs(0.0, relative_tolerance) - ); - CHECK_THAT( - relative_matrix_difference( - inverse_element_jacobian_difference, - variation.inverse_element_jacobian_variation + inverse_element_jacobian_difference, variation.inverse_element_jacobian_variation ), Catch::Matchers::WithinAbs(0.0, relative_tolerance) ); const double determinant_difference = - (plus_context.mapping.mapping_determinant - - minus_context.mapping.mapping_determinant) / + (plus_context.mapping.mapping_determinant - minus_context.mapping.mapping_determinant) / (2.0 * kelvin_difference_step); const double weight_difference = - (plus_context.quadrature.weight - minus_context.quadrature.weight) / - (2.0 * kelvin_difference_step); + (plus_context.quadrature.weight - minus_context.quadrature.weight) / (2.0 * kelvin_difference_step); check_centered_difference( - variation.mapping.mapping_determinant_variation, - determinant_difference, plus_context.mapping.mapping_determinant, - minus_context.mapping.mapping_determinant, kelvin_difference_step, + variation.mapping.mapping_determinant_variation, determinant_difference, + plus_context.mapping.mapping_determinant, minus_context.mapping.mapping_determinant, kelvin_difference_step, relative_tolerance ); check_centered_difference( - variation.weight_variation, weight_difference, - plus_context.quadrature.weight, minus_context.quadrature.weight, - kelvin_difference_step, relative_tolerance + variation.weight_variation, weight_difference, plus_context.quadrature.weight, + minus_context.quadrature.weight, kelvin_difference_step, relative_tolerance ); } - for (int boundary_element = 0; boundary_element < mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *face_transformation = - mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *face_transformation = mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(face_transformation != nullptr); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(face_transformation->GetGeometryType(), 4); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(face_transformation->GetGeometryType(), 4); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::FaceMappingContext base_context; mapping::FaceMappingContext plus_context; @@ -2663,72 +2079,57 @@ TEST_CASE( REQUIRE( mapper.EvaluateFace( - base_data.Get(), *face_transformation, - mapping::FaceElementSide::element_1, integration_point, + base_data.Get(), *face_transformation, mapping::FaceElementSide::element_1, integration_point, workspace, base_context ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFaceVariation( - base_data.Get(), direction, *face_transformation, - mapping::FaceElementSide::element_1, integration_point, - base_context, workspace, variation + base_data.Get(), direction, *face_transformation, mapping::FaceElementSide::element_1, + integration_point, base_context, workspace, variation ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFace( - plus_data.Get(), *face_transformation, - mapping::FaceElementSide::element_1, integration_point, + plus_data.Get(), *face_transformation, mapping::FaceElementSide::element_1, integration_point, workspace, plus_context ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFace( - minus_data.Get(), *face_transformation, - mapping::FaceElementSide::element_1, integration_point, + minus_data.Get(), *face_transformation, mapping::FaceElementSide::element_1, integration_point, workspace, minus_context ) == mapping::MappingStatus::valid ); CAPTURE(boundary_element, q); - const mfem::Vector normal_difference = vector_central_difference( - plus_context.quadrature.normal, minus_context.quadrature.normal - ); + const mfem::Vector normal_difference = + vector_central_difference(plus_context.quadrature.normal, minus_context.quadrature.normal); - REQUIRE( - normal_difference.Size() == - variation.physical_normal_variation.Size() - ); + REQUIRE(normal_difference.Size() == variation.physical_normal_variation.Size()); for (int i = 0; i < normal_difference.Size(); ++i) { check_centered_difference( - variation.physical_normal_variation(i), - normal_difference(i), plus_context.quadrature.normal(i), - minus_context.quadrature.normal(i), kelvin_difference_step, - relative_tolerance + variation.physical_normal_variation(i), normal_difference(i), plus_context.quadrature.normal(i), + minus_context.quadrature.normal(i), kelvin_difference_step, relative_tolerance ); } const double surface_weight_difference = - (plus_context.physical_surface_weight - - minus_context.physical_surface_weight) / + (plus_context.physical_surface_weight - minus_context.physical_surface_weight) / (2.0 * kelvin_difference_step); const double normal_scale_difference = - (plus_context.quadrature.v_dot_n_scale - - minus_context.quadrature.v_dot_n_scale) / + (plus_context.quadrature.v_dot_n_scale - minus_context.quadrature.v_dot_n_scale) / (2.0 * kelvin_difference_step); check_centered_difference( - variation.physical_surface_weight_variation, - surface_weight_difference, plus_context.physical_surface_weight, - minus_context.physical_surface_weight, kelvin_difference_step, + variation.physical_surface_weight_variation, surface_weight_difference, + plus_context.physical_surface_weight, minus_context.physical_surface_weight, kelvin_difference_step, relative_tolerance ); check_centered_difference( - variation.normal_flux_scale_variation, normal_scale_difference, - plus_context.quadrature.v_dot_n_scale, - minus_context.quadrature.v_dot_n_scale, kelvin_difference_step, - relative_tolerance + variation.normal_flux_scale_variation, normal_scale_difference, plus_context.quadrature.v_dot_n_scale, + minus_context.quadrature.v_dot_n_scale, kelvin_difference_step, relative_tolerance ); } } @@ -2749,64 +2150,51 @@ TEST_CASE( mesh_config.r_star = r_star; mesh_config.r_infinity = r_infinity; mesh_config.flattening = 0.0; - mesh_config.optimization_methods = - stroid::config::OptimizationMethods{false, true}; + mesh_config.optimization_methods = stroid::config::OptimizationMethods{false, true}; - stroid::StroidMesh stroid_mesh = stroid::GenerateMesh(mesh_config); - mfem::Mesh &mesh = *stroid_mesh.mesh; + stroid::StroidMesh stroid_mesh = stroid::GenerateMesh(mesh_config); + mfem::Mesh &mesh = *stroid_mesh.mesh; REQUIRE(stroid_mesh.exterior_coordinate != nullptr); REQUIRE(stroid_mesh.exterior_coordinate->space != nullptr); REQUIRE(stroid_mesh.exterior_coordinate->values != nullptr); - mfem::FiniteElementSpace &compactification_space = - *stroid_mesh.exterior_coordinate->space; - mfem::GridFunction &compactification_coordinate = - *stroid_mesh.exterior_coordinate->values; + mfem::FiniteElementSpace &compactification_space = *stroid_mesh.exterior_coordinate->space; + mfem::GridFunction &compactification_coordinate = *stroid_mesh.exterior_coordinate->values; mfem::H1_FECollection displacement_collection(2, dimension); - mfem::FiniteElementSpace displacement_space( - &mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM - ); + mfem::FiniteElementSpace displacement_space(&mesh, &displacement_collection, dimension, mfem::Ordering::byVDIM); - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = 0.08 * position(0) + 0.02 * position(1); value(1) = -0.03 * position(0) - 0.02 * position(1); value(2) = 0.04 * position(2); }; - auto direction_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto direction_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = 0.02 * position(0); value(1) = -0.01 * position(1) + 0.005 * position(2); value(2) = 0.015 * position(2); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dimension, displacement_function - ); - mfem::VectorFunctionCoefficient direction_coefficient( - dimension, direction_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dimension, displacement_function); + mfem::VectorFunctionCoefficient direction_coefficient(dimension, direction_function); mfem::GridFunction displacement(&displacement_space); mfem::GridFunction direction(&displacement_space); displacement.ProjectCoefficient(displacement_coefficient); direction.ProjectCoefficient(direction_coefficient); - std::unique_ptr - exterior_map = std::make_unique< - mapping::compactification::KelvinCompactification>( + std::unique_ptr exterior_map = + std::make_unique( mapping::compactification::options::KelvinCompactificationOptions{ .r_star_ref = r_star, .r_inf_ref = r_infinity } ); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - std::move(exterior_map) + {.dimension = dimension, .vacuum_element_attribute = 3}, std::move(exterior_map) ); mapping::DomainMapperStateless::Workspace workspace(dimension); @@ -2814,19 +2202,15 @@ TEST_CASE( int stellar_vacuum_faces = 0; for (int face_id = 0; face_id < mesh.GetNumFaces(); ++face_id) { - mfem::FaceElementTransformations *transformation = - mesh.GetFaceElementTransformations(face_id); - if (transformation == nullptr || transformation->Elem1 == nullptr || - transformation->Elem2 == nullptr) + mfem::FaceElementTransformations *transformation = mesh.GetFaceElementTransformations(face_id); + if (transformation == nullptr || transformation->Elem1 == nullptr || transformation->Elem2 == nullptr) continue; const int attribute_1 = transformation->Elem1->Attribute; const int attribute_2 = transformation->Elem2->Attribute; const bool core_envelope_interface = - (attribute_1 == 1 && attribute_2 == 2) || - (attribute_1 == 2 && attribute_2 == 1); - const bool stellar_vacuum_interface = - (attribute_1 == 3) != (attribute_2 == 3); + (attribute_1 == 1 && attribute_2 == 2) || (attribute_1 == 2 && attribute_2 == 1); + const bool stellar_vacuum_interface = (attribute_1 == 3) != (attribute_2 == 3); if (!core_envelope_interface && !stellar_vacuum_interface) continue; @@ -2856,51 +2240,31 @@ TEST_CASE( displacement.GetSubVector(vdofs_2, displacement_dofs_2); direction.GetSubVector(vdofs_1, direction_dofs_1); direction.GetSubVector(vdofs_2, direction_dofs_2); - compactification_space.GetElementDofs( - element_1, compactification_dof_indices_1 - ); - compactification_space.GetElementDofs( - element_2, compactification_dof_indices_2 - ); - compactification_coordinate.GetSubVector( - compactification_dof_indices_1, compactification_dofs_1 - ); - compactification_coordinate.GetSubVector( - compactification_dof_indices_2, compactification_dofs_2 - ); + compactification_space.GetElementDofs(element_1, compactification_dof_indices_1); + compactification_space.GetElementDofs(element_2, compactification_dof_indices_2); + compactification_coordinate.GetSubVector(compactification_dof_indices_1, compactification_dofs_1); + compactification_coordinate.GetSubVector(compactification_dof_indices_2, compactification_dofs_2); const mapping::ElementDisplacementData displacement_1 = - mapping::ElementDisplacementDataFromElementVDofs( - *displacement_space.GetFE(element_1), displacement_dofs_1 - ); + mapping::ElementDisplacementDataFromElementVDofs(*displacement_space.GetFE(element_1), displacement_dofs_1); const mapping::ElementDisplacementData displacement_2 = - mapping::ElementDisplacementDataFromElementVDofs( - *displacement_space.GetFE(element_2), displacement_dofs_2 - ); + mapping::ElementDisplacementDataFromElementVDofs(*displacement_space.GetFE(element_2), displacement_dofs_2); const mapping::ElementDisplacementData direction_1 = - mapping::ElementDisplacementDataFromElementVDofs( - *displacement_space.GetFE(element_1), direction_dofs_1 - ); + mapping::ElementDisplacementDataFromElementVDofs(*displacement_space.GetFE(element_1), direction_dofs_1); const mapping::ElementDisplacementData direction_2 = - mapping::ElementDisplacementDataFromElementVDofs( - *displacement_space.GetFE(element_2), direction_dofs_2 - ); + mapping::ElementDisplacementDataFromElementVDofs(*displacement_space.GetFE(element_2), direction_dofs_2); const ElementMappingDataOwner element_data_1( - displacement_1, *compactification_space.GetFE(element_1), - compactification_dofs_1 + displacement_1, *compactification_space.GetFE(element_1), compactification_dofs_1 ); const ElementMappingDataOwner element_data_2( - displacement_2, *compactification_space.GetFE(element_2), - compactification_dofs_2 + displacement_2, *compactification_space.GetFE(element_2), compactification_dofs_2 ); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 6); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 6); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); mapping::FaceMappingContext context_1; mapping::FaceMappingContext context_2; @@ -2908,13 +2272,11 @@ TEST_CASE( mapping::FaceMappingVariation variation_2; auto status_1 = mapper.EvaluateFace( - element_data_1.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + element_data_1.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, context_1 ); auto status_2 = mapper.EvaluateFace( - element_data_2.Get(), *transformation, - mapping::FaceElementSide::element_2, integration_point, + element_data_2.Get(), *transformation, mapping::FaceElementSide::element_2, integration_point, workspace, context_2 ); INFO("Element 1 ID = " << transformation->Elem1No); @@ -2924,51 +2286,34 @@ TEST_CASE( INFO("Element 2 status = " << static_cast(status_2)); mfem::Vector reference_position(dimension); - transformation->Elem2->Transform( - transformation->Elem2->GetIntPoint(), reference_position - ); + transformation->Elem2->Transform(transformation->Elem2->GetIntPoint(), reference_position); INFO( std::format( - "Reference position = <{},{},{}>", reference_position(0), - reference_position(1), reference_position(2) - ) - ); - INFO( - std::format( - "Reference radius = {}", reference_position.Norml2() - ) - ); - INFO( - std::format( - "Reference radius minus r_star = {}", - reference_position.Norml2() - r_star + "Reference position = <{},{},{}>", reference_position(0), reference_position(1), + reference_position(2) ) ); + INFO(std::format("Reference radius = {}", reference_position.Norml2())); + INFO(std::format("Reference radius minus r_star = {}", reference_position.Norml2() - r_star)); REQUIRE(status_1 == mapping::MappingStatus::valid); REQUIRE(status_2 == mapping::MappingStatus::valid); REQUIRE( mapper.EvaluateFaceVariation( - element_data_1.Get(), direction_1, *transformation, - mapping::FaceElementSide::element_1, integration_point, - context_1, workspace, variation_1 + element_data_1.Get(), direction_1, *transformation, mapping::FaceElementSide::element_1, + integration_point, context_1, workspace, variation_1 ) == mapping::MappingStatus::valid ); REQUIRE( mapper.EvaluateFaceVariation( - element_data_2.Get(), direction_2, *transformation, - mapping::FaceElementSide::element_2, integration_point, - context_2, workspace, variation_2 + element_data_2.Get(), direction_2, *transformation, mapping::FaceElementSide::element_2, + integration_point, context_2, workspace, variation_2 ) == mapping::MappingStatus::valid ); CAPTURE(face_id, q, attribute_1, attribute_2); - check_vector( - context_1.mapping.physical_position, - context_2.mapping.physical_position, interface_tolerance - ); + check_vector(context_1.mapping.physical_position, context_2.mapping.physical_position, interface_tolerance); check_scalar_relative( - context_1.physical_surface_weight, - context_2.physical_surface_weight, interface_tolerance + context_1.physical_surface_weight, context_2.physical_surface_weight, interface_tolerance ); mfem::Vector normal_sum(context_1.quadrature.normal); @@ -2976,19 +2321,15 @@ TEST_CASE( CHECK(normal_sum.Norml2() < interface_tolerance); check_vector( - variation_1.mapping.physical_position_variation, - variation_2.mapping.physical_position_variation, + variation_1.mapping.physical_position_variation, variation_2.mapping.physical_position_variation, interface_tolerance ); check_scalar_relative( - variation_1.physical_surface_weight_variation, - variation_2.physical_surface_weight_variation, + variation_1.physical_surface_weight_variation, variation_2.physical_surface_weight_variation, interface_tolerance ); - mfem::Vector normal_variation_sum( - variation_1.physical_normal_variation - ); + mfem::Vector normal_variation_sum(variation_1.physical_normal_variation); normal_variation_sum += variation_2.physical_normal_variation; CHECK(normal_variation_sum.Norml2() < interface_tolerance); @@ -2996,19 +2337,11 @@ TEST_CASE( mfem::Vector reference_flux_1; mfem::Vector reference_flux_2; - mapping::MapPhysicalFluxToHDivReference( - context_1.mapping, physical_flux, reference_flux_1 - ); - mapping::MapPhysicalFluxToHDivReference( - context_2.mapping, physical_flux, reference_flux_2 - ); + mapping::MapPhysicalFluxToHDivReference(context_1.mapping, physical_flux, reference_flux_1); + mapping::MapPhysicalFluxToHDivReference(context_2.mapping, physical_flux, reference_flux_2); - const double flux_1 = - (reference_flux_1 * context_1.reference_normal) * - context_1.reference_surface_weight; - const double flux_2 = - (reference_flux_2 * context_2.reference_normal) * - context_2.reference_surface_weight; + const double flux_1 = (reference_flux_1 * context_1.reference_normal) * context_1.reference_surface_weight; + const double flux_2 = (reference_flux_2 * context_2.reference_normal) * context_2.reference_surface_weight; CHECK_THAT(flux_1 + flux_2, WithinAbs(0.0, interface_tolerance)); } @@ -3029,125 +2362,80 @@ TEST_CASE( QuadraticElementFixture fixture; - const mfem::FiniteElement &element = fixture.GetElement(); - mfem::ElementTransformation *element_transformation = - fixture.mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = fixture.GetElement(); + mfem::ElementTransformation *element_transformation = fixture.mesh.GetElementTransformation(0); - const mfem::Vector displacement_dofs = make_function_element_dofs( - element, *element_transformation, evaluate_quadratic_displacement, - mfem::Ordering::byVDIM - ); - const mapping::ElementDisplacementData displacement( - element, displacement_dofs, mfem::Ordering::byVDIM + const mfem::Vector displacement_dofs = make_function_element_dofs( + element, *element_transformation, evaluate_quadratic_displacement, mfem::Ordering::byVDIM ); + const mapping::ElementDisplacementData displacement(element, displacement_dofs, mfem::Ordering::byVDIM); const ElementMappingDataOwner element_data(displacement); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); - double reference_divergence_integral = 0.0; - double physical_divergence_integral = 0.0; - double physical_boundary_flux = 0.0; + double reference_divergence_integral = 0.0; + double physical_divergence_integral = 0.0; + double physical_boundary_flux = 0.0; - const mfem::IntegrationRule &volume_rule = - mfem::IntRules.Get(element_transformation->GetGeometryType(), 10); + const mfem::IntegrationRule &volume_rule = mfem::IntRules.Get(element_transformation->GetGeometryType(), 10); for (int q = 0; q < volume_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - volume_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = volume_rule.IntPoint(q); mapping::VolumeMappingContext context; REQUIRE( - mapper.EvaluateVolume( - element_data.Get(), *element_transformation, integration_point, - workspace, context - ) == mapping::MappingStatus::valid + mapper.EvaluateVolume(element_data.Get(), *element_transformation, integration_point, workspace, context) == + mapping::MappingStatus::valid ); element_transformation->SetIntPoint(&integration_point); - const double reference_divergence = evaluate_reference_hdiv_divergence( - context.mapping.reference_position - ); - const double physical_divergence = mapping::MapHDivDivergenceToPhysical( - context.mapping, reference_divergence - ); - const double reference_weight = - integration_point.weight * element_transformation->Weight(); + const double reference_divergence = evaluate_reference_hdiv_divergence(context.mapping.reference_position); + const double physical_divergence = mapping::MapHDivDivergenceToPhysical(context.mapping, reference_divergence); + const double reference_weight = integration_point.weight * element_transformation->Weight(); - reference_divergence_integral += - reference_divergence * reference_weight; - physical_divergence_integral += - physical_divergence * context.quadrature.weight; + reference_divergence_integral += reference_divergence * reference_weight; + physical_divergence_integral += physical_divergence * context.quadrature.weight; } - for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); - ++boundary_element) { - mfem::FaceElementTransformations *transformation = - fixture.mesh.GetBdrFaceTransformations(boundary_element); + for (int boundary_element = 0; boundary_element < fixture.mesh.GetNBE(); ++boundary_element) { + mfem::FaceElementTransformations *transformation = fixture.mesh.GetBdrFaceTransformations(boundary_element); REQUIRE(transformation != nullptr); - const mfem::IntegrationRule &face_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 10); + const mfem::IntegrationRule &face_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 10); for (int q = 0; q < face_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - face_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = face_rule.IntPoint(q); mapping::FaceMappingContext context; REQUIRE( mapper.EvaluateFace( - element_data.Get(), *transformation, - mapping::FaceElementSide::element_1, integration_point, + element_data.Get(), *transformation, mapping::FaceElementSide::element_1, integration_point, workspace, context ) == mapping::MappingStatus::valid ); - const mfem::Vector reference_flux = evaluate_reference_hdiv_field( - context.mapping.reference_position - ); + const mfem::Vector reference_flux = evaluate_reference_hdiv_field(context.mapping.reference_position); mfem::Vector physical_flux; - mapping::MapHDivFluxToPhysical( - context.mapping, reference_flux, physical_flux - ); + mapping::MapHDivFluxToPhysical(context.mapping, reference_flux, physical_flux); - physical_boundary_flux += - (physical_flux * context.quadrature.normal) * - context.physical_surface_weight; + physical_boundary_flux += (physical_flux * context.quadrature.normal) * context.physical_surface_weight; } } constexpr double analytic_reference_integral = 216.0; - INFO( - "Analytic reference divergence integral = " - << analytic_reference_integral - ); - INFO( - "Computed reference divergence integral = " - << reference_divergence_integral - ); - INFO( - "Computed physical divergence integral = " - << physical_divergence_integral - ); + INFO("Analytic reference divergence integral = " << analytic_reference_integral); + INFO("Computed reference divergence integral = " << reference_divergence_integral); + INFO("Computed physical divergence integral = " << physical_divergence_integral); INFO("Computed physical boundary flux = " << physical_boundary_flux); - CHECK_THAT( - reference_divergence_integral, - WithinAbs(analytic_reference_integral, divergence_tolerance) - ); - CHECK_THAT( - physical_divergence_integral, - WithinAbs(analytic_reference_integral, divergence_tolerance) - ); - CHECK_THAT( - physical_boundary_flux, - WithinAbs(analytic_reference_integral, divergence_tolerance) - ); + CHECK_THAT(reference_divergence_integral, WithinAbs(analytic_reference_integral, divergence_tolerance)); + CHECK_THAT(physical_divergence_integral, WithinAbs(analytic_reference_integral, divergence_tolerance)); + CHECK_THAT(physical_boundary_flux, WithinAbs(analytic_reference_integral, divergence_tolerance)); } TEST_CASE( @@ -3172,9 +2460,7 @@ TEST_CASE( REQUIRE(context.mapping_determinant > 0.0); context.inverse_mapping_jacobian.SetSize(3); - mfem::CalcInverse( - context.mapping_jacobian, context.inverse_mapping_jacobian - ); + mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian); const mfem::Vector reference_field = make_vector(0.7, -0.4, 1.1); const mfem::Vector reference_test_field = make_vector(-0.2, 0.9, 0.5); @@ -3199,32 +2485,21 @@ TEST_CASE( mfem::Vector recovered_curl; mapping::MapHCurlFieldToPhysical(context, reference_field, physical_field); - mapping::MapPhysicalFieldToHCurlReference( - context, physical_field, recovered_field - ); - mapping::MapHCurlFieldToPhysical( - context, reference_test_field, physical_test_field - ); + mapping::MapPhysicalFieldToHCurlReference(context, physical_field, recovered_field); + mapping::MapHCurlFieldToPhysical(context, reference_test_field, physical_test_field); mapping::MapHCurlCurlToPhysical(context, reference_curl, physical_curl); - mapping::MapPhysicalCurlToHCurlReference( - context, physical_curl, recovered_curl - ); + mapping::MapPhysicalCurlToHCurlReference(context, physical_curl, recovered_curl); check_vector(recovered_field, reference_field, curl_tolerance); check_vector(recovered_curl, reference_curl, curl_tolerance); mfem::DenseMatrix temporary(3); mfem::DenseMatrix physical_gradient(3); - mfem::MultAtB( - context.inverse_mapping_jacobian, reference_gradient, temporary - ); + mfem::MultAtB(context.inverse_mapping_jacobian, reference_gradient, temporary); mfem::Mult(temporary, context.inverse_mapping_jacobian, physical_gradient); - const mfem::Vector directly_computed_physical_curl = - matrix_curl(physical_gradient); - check_vector( - directly_computed_physical_curl, physical_curl, curl_tolerance - ); + const mfem::Vector directly_computed_physical_curl = matrix_curl(physical_gradient); + check_vector(directly_computed_physical_curl, physical_curl, curl_tolerance); mfem::DenseMatrix mass_tensor; mfem::DenseMatrix curl_tensor; @@ -3234,32 +2509,22 @@ TEST_CASE( mfem::Vector mass_action(3); mass_tensor.Mult(reference_test_field, mass_action); - const double physical_mass_inner_product = - context.mapping_determinant * (physical_field * physical_test_field); + const double physical_mass_inner_product = context.mapping_determinant * (physical_field * physical_test_field); const double reference_mass_inner_product = reference_field * mass_action; - CHECK_THAT( - physical_mass_inner_product, - WithinAbs(reference_mass_inner_product, curl_tolerance) - ); + CHECK_THAT(physical_mass_inner_product, WithinAbs(reference_mass_inner_product, curl_tolerance)); const mfem::Vector reference_test_curl = make_vector(-0.3, 0.6, 0.2); mfem::Vector physical_test_curl; mfem::Vector curl_action(3); - mapping::MapHCurlCurlToPhysical( - context, reference_test_curl, physical_test_curl - ); + mapping::MapHCurlCurlToPhysical(context, reference_test_curl, physical_test_curl); curl_tensor.Mult(reference_test_curl, curl_action); - const double physical_curl_inner_product = - context.mapping_determinant * (physical_curl * physical_test_curl); + const double physical_curl_inner_product = context.mapping_determinant * (physical_curl * physical_test_curl); const double reference_curl_inner_product = reference_curl * curl_action; - CHECK_THAT( - physical_curl_inner_product, - WithinAbs(reference_curl_inner_product, curl_tolerance) - ); + CHECK_THAT(physical_curl_inner_product, WithinAbs(reference_curl_inner_product, curl_tolerance)); } TEST_CASE( @@ -3272,23 +2537,16 @@ TEST_CASE( constexpr double gradient_tolerance = 2.0e-12; constexpr double mapping_tolerance = 3.0e-12; - auto check_space_ordering = [](const mfem::Ordering::Type space_ordering) { - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 2, 1, 1, mfem::Element::HEXAHEDRON, 2.0, 1.5, 1.25 - ); + auto check_space_ordering = [](const mfem::Ordering::Type space_ordering) { + mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 1, 1, mfem::Element::HEXAHEDRON, 2.0, 1.5, 1.25); - mfem::H1_FECollection displacement_collection( - displacement_order, dimension - ); + mfem::H1_FECollection displacement_collection(displacement_order, dimension); - mfem::FiniteElementSpace displacement_space( - &mesh, &displacement_collection, dimension, space_ordering - ); + mfem::FiniteElementSpace displacement_space(&mesh, &displacement_collection, dimension, space_ordering); mfem::GridFunction displacement(&displacement_space); - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { const double x = position(0); const double y = position(1); const double z = position(2); @@ -3302,15 +2560,12 @@ TEST_CASE( value(2) = 0.31 - 0.04 * x + 0.08 * z - 0.015 * x * y; }; - mfem::VectorFunctionCoefficient displacement_coefficient( - dimension, displacement_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(dimension, displacement_function); displacement.ProjectCoefficient(displacement_coefficient); mapping::DomainMapperStateless mapper( - {.dimension = dimension, .vacuum_element_attribute = 3}, - make_kelvin_compactification() + {.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification() ); mapping::DomainMapperStateless::Workspace workspace(dimension); @@ -3319,18 +2574,15 @@ TEST_CASE( REQUIRE(displacement.VectorDim() == dimension); for (int element_id = 0; element_id < mesh.GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(element_id); REQUIRE(transformation != nullptr); - const mfem::FiniteElement &displacement_element = - *displacement_space.GetFE(element_id); + const mfem::FiniteElement &displacement_element = *displacement_space.GetFE(element_id); mfem::Array element_vdofs; - mfem::DofTransformation *dof_transformation = - displacement_space.GetElementVDofs(element_id, element_vdofs); + mfem::DofTransformation *dof_transformation = displacement_space.GetElementVDofs(element_id, element_vdofs); mfem::Vector element_displacement; @@ -3341,23 +2593,18 @@ TEST_CASE( } const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); REQUIRE(displacement_data.GetOrdering() == mfem::Ordering::byNODES); const ElementMappingDataOwner element_data(displacement_data); - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); mfem::Vector shape(displacement_element.GetDof()); - mfem::DenseMatrix physical_dshape( - displacement_element.GetDof(), dimension - ); + mfem::DenseMatrix physical_dshape(displacement_element.GetDof(), dimension); mfem::Vector computed_value(dimension); mfem::Vector expected_value(dimension); @@ -3367,8 +2614,7 @@ TEST_CASE( mfem::DenseMatrix expected_gradient(dimension, dimension); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); CAPTURE(static_cast(space_ordering), element_id, q); @@ -3376,18 +2622,11 @@ TEST_CASE( displacement_element.CalcShape(integration_point, shape); - displacement_element.CalcPhysDShape( - *transformation, physical_dshape - ); + displacement_element.CalcPhysDShape(*transformation, physical_dshape); - displacement_data.GetDofMatrix().MultTranspose( - shape, computed_value - ); + displacement_data.GetDofMatrix().MultTranspose(shape, computed_value); - mfem::MultAtB( - displacement_data.GetDofMatrix(), physical_dshape, - computed_gradient - ); + mfem::MultAtB(displacement_data.GetDofMatrix(), physical_dshape, computed_gradient); /* * Use MFEM's native evaluation as the authoritative @@ -3395,21 +2634,15 @@ TEST_CASE( */ transformation->SetIntPoint(&integration_point); - displacement.GetVectorValue( - *transformation, integration_point, expected_value - ); + displacement.GetVectorValue(*transformation, integration_point, expected_value); transformation->SetIntPoint(&integration_point); - displacement.GetVectorGradient( - *transformation, expected_gradient - ); + displacement.GetVectorGradient(*transformation, expected_gradient); check_vector(computed_value, expected_value, value_tolerance); - check_matrix( - computed_gradient, expected_gradient, gradient_tolerance - ); + check_matrix(computed_gradient, expected_gradient, gradient_tolerance); /* * Also exercise the complete stateless-mapper path. @@ -3418,57 +2651,35 @@ TEST_CASE( */ mapping::MappingPointContext context; - const mapping::MappingStatus status = mapper.EvaluatePoint( - element_data.Get(), *transformation, integration_point, - workspace, context - ); + const mapping::MappingStatus status = + mapper.EvaluatePoint(element_data.Get(), *transformation, integration_point, workspace, context); REQUIRE(status == mean_field::mapping::MappingStatus::valid); REQUIRE_FALSE(context.compactified); - mfem::Vector expected_displaced_position( - context.reference_position - ); + mfem::Vector expected_displaced_position(context.reference_position); expected_displaced_position += expected_value; - mfem::DenseMatrix expected_displacement_jacobian( - expected_gradient - ); + mfem::DenseMatrix expected_displacement_jacobian(expected_gradient); for (int d = 0; d < dimension; ++d) { expected_displacement_jacobian(d, d) += 1.0; } - check_vector( - context.displaced_position, expected_displaced_position, - mapping_tolerance - ); + check_vector(context.displaced_position, expected_displaced_position, mapping_tolerance); - check_vector( - context.physical_position, expected_displaced_position, - mapping_tolerance - ); + check_vector(context.physical_position, expected_displaced_position, mapping_tolerance); - check_matrix( - context.displacement_jacobian, - expected_displacement_jacobian, mapping_tolerance - ); + check_matrix(context.displacement_jacobian, expected_displacement_jacobian, mapping_tolerance); - check_matrix( - context.mapping_jacobian, expected_displacement_jacobian, - mapping_tolerance - ); + check_matrix(context.mapping_jacobian, expected_displacement_jacobian, mapping_tolerance); - const double expected_determinant = - expected_displacement_jacobian.Det(); + const double expected_determinant = expected_displacement_jacobian.Det(); CHECK_THAT( - context.mapping_determinant, - Catch::Matchers::WithinAbs( - expected_determinant, mapping_tolerance - ) + context.mapping_determinant, Catch::Matchers::WithinAbs(expected_determinant, mapping_tolerance) ); REQUIRE(context.mapping_determinant > 0.0); diff --git a/tests/mapping/hdiv_mass_tensor.cpp b/tests/mapping/hdiv_mass_tensor.cpp index 1f82693..6da9b35 100644 --- a/tests/mapping/hdiv_mass_tensor.cpp +++ b/tests/mapping/hdiv_mass_tensor.cpp @@ -38,8 +38,7 @@ namespace { return identity; } - mapping::MappingPointContext - make_context(const mfem::DenseMatrix &jacobian) { + mapping::MappingPointContext make_context(const mfem::DenseMatrix &jacobian) { mapping::MappingPointContext context; context.mapping_jacobian = jacobian; context.mapping_determinant = jacobian.Det(); @@ -70,9 +69,8 @@ namespace { const mfem::DenseMatrix &jacobian_variation ) { mapping::MappingPointVariation variation; - variation.mapping_jacobian_variation = jacobian_variation; - variation.mapping_determinant_variation = - determinant_variation(jacobian, jacobian_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; @@ -99,11 +97,7 @@ namespace { mfem::DenseMatrix difference(computed); difference -= reference; - return matrix_norm(difference) / - std::max( - matrix_norm(reference), - std::numeric_limits::epsilon() - ); + return matrix_norm(difference) / std::max(matrix_norm(reference), std::numeric_limits::epsilon()); } double matrix_asymmetry(const mfem::DenseMatrix &matrix) { @@ -111,8 +105,7 @@ namespace { 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); + const double difference = matrix(row, column) - matrix(column, row); asymmetry_squared += difference * difference; } } @@ -130,20 +123,16 @@ namespace { 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); + 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); + 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; @@ -184,22 +173,17 @@ TEST_CASE( ); 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}), + {"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} - )} + {"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}), + {"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})} ); @@ -207,37 +191,22 @@ TEST_CASE( 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 - ); + 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 - ); + 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); + 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("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); @@ -252,31 +221,24 @@ TEST_CASE( "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 = 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); + 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 - ); + 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); + 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]); } @@ -295,24 +257,20 @@ 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}); + 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; + zero_jacobian_variation = 0.0; - mapping::MappingPointContext context = make_context(jacobian); - mapping::MappingPointVariation variation = - make_variation(jacobian, zero_jacobian_variation); + 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 - ); + 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); @@ -323,23 +281,17 @@ TEST_CASE( "Identity", tags::unit &tags::transformations ) { - const mfem::DenseMatrix identity = make_identity_matrix(); + 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 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); + const mapping::MappingPointVariation variation = make_variation(identity, rotation_variation); mfem::DenseMatrix tensor_variation; - mapping::ComputeHDivMassTensorVariation( - context, variation, tensor_variation - ); + mapping::ComputeHDivMassTensorVariation(context, variation, tensor_variation); - CHECK_THAT( - variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15) - ); + CHECK_THAT(variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)); check_zero_matrix(tensor_variation, 1.0e-14); } @@ -355,26 +307,18 @@ TEST_CASE( 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); + 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 - ); + mapping::ComputeHDivMassTensorVariation(context, variation, tensor_variation); - CHECK_THAT( - variation.mapping_determinant_variation, - WithinAbs(3.0 * scaling_variation, 1.0e-14) - ); + 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) - ); + CHECK_THAT(tensor_variation(row, column), WithinAbs(expected, 1.0e-14)); } } } @@ -387,21 +331,16 @@ TEST_CASE( const mfem::DenseMatrix identity = make_identity_matrix(); mfem::DenseMatrix jacobian_variation(dimension); - jacobian_variation = 0.0; - jacobian_variation(0, 1) = shear_variation; + 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); + 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 - ); + mapping::ComputeHDivMassTensorVariation(context, variation, tensor_variation); - CHECK_THAT( - variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15) - ); + 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)); @@ -418,16 +357,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 = 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; + 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); @@ -439,12 +376,10 @@ TEST_CASE( 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); + 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); diff --git a/tests/models/stellar_model.cpp b/tests/models/stellar_model.cpp new file mode 100644 index 0000000..fc9e372 --- /dev/null +++ b/tests/models/stellar_model.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include + +#include + +import mean_field; +import test_helpers; + +namespace { + struct StellarModelExtensionTracker final { + int structureValidationCount{0}; + int surfaceValidationCount{0}; + int surfaceResolutionCount{0}; + + const mean_field::eos::EquationOfState *structureEquationOfState{nullptr}; + + const mean_field::eos::EquationOfState *surfaceValidationEquationOfState{nullptr}; + + const mean_field::eos::EquationOfState *surfaceResolutionEquationOfState{nullptr}; + }; + + class StellarModelTestStructure final : public mean_field::models::structure::StructureBase { + public: + explicit StellarModelTestStructure(std::shared_ptr tracker) + : m_tracker(std::move(tracker)), + m_equationOfState( + 3.0, + 0.25 + ) { + } + + [[nodiscard]] const mean_field::eos::EquationOfState &equationOfState() const noexcept override { + m_tracker->structureEquationOfState = &m_equationOfState; + return m_equationOfState; + } + + [[nodiscard]] double targetMass() const noexcept override { + return 2.5; + } + + [[nodiscard]] mean_field::models::structure::StructureSeed + makeInitialSeed(const mean_field::models::structure::StructureSeedRequest &request) const override { + mean_field::models::structure::StructureSeed seed; + + seed.radius.SetSize(2); + seed.density.SetSize(2); + seed.enthalpy.SetSize(2); + + seed.radius(0) = 0.0; + seed.radius(1) = 1.0; + + seed.density(0) = request.centralDensity; + seed.density(1) = 0.0; + + seed.enthalpy(0) = 1.0; + seed.enthalpy(1) = 0.0; + + seed.stellarRadius = 1.0; + seed.centralDensity = request.centralDensity; + seed.centralEnthalpy = 1.0; + + return seed; + } + + void validate() const override { + ++m_tracker->structureValidationCount; + } + + private: + std::shared_ptr m_tracker; + mean_field::eos::Polytrope m_equationOfState; + }; + + class StellarModelTestSurface final : public mean_field::surface::SurfaceBase { + public: + explicit StellarModelTestSurface(std::shared_ptr tracker) + : m_tracker(std::move(tracker)) { + } + + [[nodiscard]] + mean_field::surface::ResolvedSurfaceCondition + resolve(const mean_field::eos::EquationOfState &equationOfState) const override { + ++m_tracker->surfaceResolutionCount; + + m_tracker->surfaceResolutionEquationOfState = &equationOfState; + + return mean_field::surface::ResolvedSurfaceCondition{0.375}; + } + + void validate(const mean_field::eos::EquationOfState &equationOfState) const override { + ++m_tracker->surfaceValidationCount; + + m_tracker->surfaceValidationEquationOfState = &equationOfState; + } + + private: + std::shared_ptr m_tracker; + }; +} // namespace + +TEST_CASE( + "Stellar Model Owns Structure And Surface Prescriptions", + tags::barotrope &tags::unit &tags::model +) { + STATIC_REQUIRE_FALSE(std::is_copy_constructible_v); + + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v); + + STATIC_REQUIRE(std::is_nothrow_move_constructible_v); + + STATIC_REQUIRE(std::is_nothrow_move_assignable_v); + + mean_field::models::StellarModel model{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, + mean_field::surface::Isobaric{0.0} + }; + + CHECK(model.targetMass() == 1.0); + CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.0); + + CHECK( + dynamic_cast(&model.structurePrescription()) != + nullptr + ); + + CHECK(dynamic_cast(&model.surfacePrescription()) != nullptr); +} + +TEST_CASE( + "Stellar Model Delegates Seed Construction To Its Structure", + tags::barotrope &tags::unit &tags::model +) { + mean_field::models::StellarModel model{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, + mean_field::surface::Isobaric{} + }; + + const mean_field::models::structure::StructureSeed seed = + model.makeInitialSeed({.centralDensity = 2.0, .radialSampleCount = 64}); + + CHECK(seed.radius.Size() == 64); + CHECK(seed.density.Size() == 64); + CHECK(seed.enthalpy.Size() == 64); + CHECK(seed.centralDensity == 2.0); + CHECK(seed.stellarRadius > 0.0); + CHECK(seed.density(0) == 2.0); + CHECK(seed.density(63) == 0.0); + CHECK(seed.enthalpy(63) == 0.0); +} + +TEST_CASE( + "Moving A Stellar Model Preserves Stable Prescription Addresses", + tags::barotrope &tags::unit &tags::model +) { + mean_field::models::StellarModel originalModel{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, + mean_field::surface::Isobaric{} + }; + + const mean_field::models::structure::StructureBase *structureAddress = &originalModel.structurePrescription(); + + const mean_field::surface::SurfaceBase *surfaceAddress = &originalModel.surfacePrescription(); + + const mean_field::eos::EquationOfState *equationOfStateAddress = &originalModel.equationOfState(); + + mean_field::models::StellarModel movedModel{std::move(originalModel)}; + + CHECK(&movedModel.structurePrescription() == structureAddress); + CHECK(&movedModel.surfacePrescription() == surfaceAddress); + CHECK(&movedModel.equationOfState() == equationOfStateAddress); + CHECK(movedModel.targetMass() == 1.0); +} + +TEST_CASE( + "Stellar Model Resolves A Positive Isobaric Surface", + tags::barotrope &tags::unit &tags::model +) { + constexpr double targetPressure = 0.03125; + + mean_field::models::StellarModel model{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0}, + mean_field::surface::Isobaric{targetPressure} + }; + + const double targetEnthalpy = model.resolvedSurfaceCondition().targetEnthalpy; + + CHECK(targetEnthalpy > 0.0); + CHECK( + std::abs(model.equationOfState().pressure_from_enthalpy(targetEnthalpy) - targetPressure) < + 64.0 * std::numeric_limits::epsilon() + ); +} + +TEST_CASE( + "Stellar Model Supports Custom Structure And Surface Prescriptions", + tags::barotrope &tags::unit &tags::model +) { + const auto tracker = std::make_shared(); + + mean_field::models::StellarModel model{StellarModelTestStructure{tracker}, StellarModelTestSurface{tracker}}; + + REQUIRE(tracker->structureValidationCount == 1); + REQUIRE(tracker->surfaceValidationCount == 1); + REQUIRE(tracker->surfaceResolutionCount == 1); + + CHECK(dynamic_cast(&model.structurePrescription()) != nullptr); + + CHECK(dynamic_cast(&model.surfacePrescription()) != nullptr); + + const mean_field::eos::EquationOfState *ownedEquationOfState = &model.equationOfState(); + + CHECK(tracker->structureEquationOfState == ownedEquationOfState); + + CHECK(tracker->surfaceValidationEquationOfState == ownedEquationOfState); + + CHECK(tracker->surfaceResolutionEquationOfState == ownedEquationOfState); + + CHECK(model.targetMass() == 2.5); + + CHECK(model.resolvedSurfaceCondition().targetEnthalpy == 0.375); +} + +TEST_CASE( + "Move Assignment Preserves Stellar Model Prescription Addresses", + tags::barotrope &tags::unit &tags::model +) { + mean_field::models::StellarModel sourceModel{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.25}, + mean_field::surface::Isobaric{0.0} + }; + + mean_field::models::StellarModel destinationModel{ + mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{2.0, 0.5}, 4.0}, + mean_field::surface::Isobaric{0.02} + }; + + const mean_field::models::structure::StructureBase *sourceStructureAddress = &sourceModel.structurePrescription(); + + const mean_field::surface::SurfaceBase *sourceSurfaceAddress = &sourceModel.surfacePrescription(); + + const mean_field::eos::EquationOfState *sourceEquationOfStateAddress = &sourceModel.equationOfState(); + + const double sourceTargetEnthalpy = sourceModel.resolvedSurfaceCondition().targetEnthalpy; + + destinationModel = std::move(sourceModel); + + CHECK(&destinationModel.structurePrescription() == sourceStructureAddress); + + CHECK(&destinationModel.surfacePrescription() == sourceSurfaceAddress); + + CHECK(&destinationModel.equationOfState() == sourceEquationOfStateAddress); + + CHECK(destinationModel.targetMass() == 1.25); + + CHECK(destinationModel.resolvedSurfaceCondition().targetEnthalpy == sourceTargetEnthalpy); +} \ No newline at end of file diff --git a/tests/operators/contexts/barotropic_closure_linearization_context.cpp b/tests/operators/contexts/barotropic_closure_linearization_context.cpp index e6d9ec0..de9c4fb 100644 --- a/tests/operators/contexts/barotropic_closure_linearization_context.cpp +++ b/tests/operators/contexts/barotropic_closure_linearization_context.cpp @@ -1,297 +1,416 @@ -#include - #include +#include +#include #include +#include import mean_field; import test_helpers; namespace barotropic_closure_context_test_utils { - mfem::Vector project_field( - mfem::ParFiniteElementSpace &finiteElementSpace, - mfem::Coefficient &coefficient + namespace field = mean_field::field; + namespace domain = mean_field::utils::domain; + namespace context = mean_field::operators::context::barotropic; + + 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) + ) { + } + }; + + [[nodiscard]] context::BarotropicClosureDependencies make_dependencies() { + return { + .discretization = {.identity = 101, .revision = 2}, + .density = {.identity = 103, .revision = 3}, + .enthalpy = {.identity = 107, .revision = 5}, + .displacement = {.identity = 109, .revision = 7} + }; + } + + [[nodiscard]] context::BarotropicClosureStateView make_state( + const mfem::Vector &density, + const mfem::Vector &enthalpy, + const mfem::Vector &displacement ) { - mfem::ParGridFunction field(&finiteElementSpace); - - field.ProjectCoefficient(coefficient); - - mfem::Vector trueVector; - field.GetTrueDofs(trueVector); - - return trueVector; + return {.density = density, .enthalpy = enthalpy, .displacement = displacement}; } - mfem::Vector make_density(const mean_field::fem::FEM &f) { - mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { - return 0.55 + 0.025 * position(0) - 0.010 * position(1); - }); - - return project_field(*f.densityFes, coefficient); + [[nodiscard]] mfem::Vector reduce( + const field::FieldDofMap &map, + const mfem::Vector &full + ) { + return map.gather(full); } - mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) { - mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { - return 0.90 + 0.020 * position(0) - 0.010 * position(1) + - 0.005 * position(2); + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction value(f.densityFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.71 + 0.05 * std::sin(0.63 * position(0) + phase) + 0.02 * position(1); }); + value.ProjectCoefficient(coefficient); + mfem::Vector result; + value.GetTrueDofs(result); + return result; + } - return project_field(*f.enthalpyFes, coefficient); + [[nodiscard]] mfem::Vector make_enthalpy( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction value(f.enthalpyFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.93 + 0.04 * std::cos(0.57 * position(1) - phase) + 0.015 * position(2); + }); + value.ProjectCoefficient(coefficient); + mfem::Vector result; + value.GetTrueDofs(result); + return result; + } + + [[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); } } // namespace barotropic_closure_context_test_utils TEST_CASE( - "Barotropic Closure Context Tracks Independent Revisions", - tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit + "Prepared Barotropic Closure Owns Its Linearization Context", + tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit ) { - auto args = test_utils::setup_args(); + using Operator = mean_field::operators::PreparedBarotropicClosureOperator; + using Context = mean_field::operators::context::barotropic::BarotropicClosureLinearizationContext; - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + 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); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); + REQUIRE(f.okay()); - mean_field::operators::context::barotropic:: - BarotropicClosureLinearizationContext context( - f, *f.domainMapperStateless, barotrope - ); + const mean_field::eos::Polytrope equationOfState(3.0, 1.5); + Operator preparedOperator(f, *f.domainMapperStateless, equationOfState); - mfem::Vector density = - barotropic_closure_context_test_utils::make_density(f); + CHECK_FALSE(preparedOperator.IsPrepared()); + CHECK_FALSE(preparedOperator.GetContext().IsPrepared()); + CHECK(&preparedOperator.GetContext() == &preparedOperator.GetContext()); - mfem::Vector enthalpy = - barotropic_closure_context_test_utils::make_enthalpy(f); - - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.0); - - mean_field::operators::context::barotropic::BarotropicClosureRevisions - revisions{.density = 3, .enthalpy = 5, .displacement = 7}; - - CHECK_FALSE(context.IsPrepared()); - CHECK(context.GetPreparationCount() == 0); - - context.Prepare(density, enthalpy, displacement, revisions); - - REQUIRE(context.IsPrepared()); - - CHECK(context.MatchesRevisions(revisions)); - CHECK(context.GetRevisions() == revisions); - CHECK(context.GetPreparationCount() == 1); - - CHECK(context.GetOperator().GetPreparationCount() == 1); - - context.Prepare(density, enthalpy, displacement, revisions); - - CHECK(context.GetPreparationCount() == 1); - - const double frozenDensityValue = context.GetBaseDensityTrue()(0); - - density(0) += 0.125; - - CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue); - - context.Prepare(density, enthalpy, displacement, revisions); - - CHECK(context.GetPreparationCount() == 1); - CHECK(context.GetBaseDensityTrue()(0) == frozenDensityValue); - - ++revisions.density; - - context.Prepare(density, enthalpy, displacement, revisions); - - CHECK(context.GetPreparationCount() == 2); - CHECK(context.GetBaseDensityTrue()(0) == density(0)); - - enthalpy(0) += 0.050; - ++revisions.enthalpy; - - context.Prepare(density, enthalpy, displacement, revisions); - - CHECK(context.GetPreparationCount() == 3); - CHECK(context.GetBaseEnthalpyTrue()(0) == enthalpy(0)); - - displacement = gravity_prepared_test_utils::make_displacement(f, 1.0); - - ++revisions.displacement; - - context.Prepare(density, enthalpy, displacement, revisions); - - CHECK(context.GetPreparationCount() == 4); - CHECK(context.GetRevisions() == revisions); - CHECK(context.MatchesRevisions(revisions)); - - CHECK(context.GetOperator().GetPreparationCount() == 4); + const auto statistics = preparedOperator.GetContextPreparationStatistics(); + CHECK(statistics.staticPreparations == 0); + CHECK(statistics.geometryPreparations == 0); + CHECK(statistics.baseStatePreparations == 0); } TEST_CASE( - "Barotropic Closure Context Reprepares A Consistent Frozen State", - tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::unit + "Barotropic Closure Context Applies Selective Invalidation In Reduced Field Coordinates", + tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit ) { - auto args = test_utils::setup_args(); + using namespace barotropic_closure_context_test_utils; - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + REQUIRE(f.okay()); - mean_field::operators::context::barotropic:: - BarotropicClosureLinearizationContext context( - f, *f.domainMapperStateless, barotrope - ); + const Maps maps(f); - const mfem::Vector density = - barotropic_closure_context_test_utils::make_density(f); + const mean_field::eos::Polytrope equationOfState(3.0, 1.5); - const mfem::Vector enthalpy = - barotropic_closure_context_test_utils::make_enthalpy(f); - - const mfem::Vector identityDisplacement = - gravity_prepared_test_utils::make_displacement(f, 0.0); - - const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.37 - ); - - const mfem::Vector enthalpyVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.71 - ); - - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.displacementFes->GetTrueVSize(), 0.37 - ); - - mean_field::operators::context::barotropic::BarotropicClosureRevisions - revisions{.density = 11, .enthalpy = 13, .displacement = 17}; - - context.Prepare(density, enthalpy, identityDisplacement, revisions); - - mfem::Vector initialResidual; - mfem::Vector initialAction; - - context.BuildResidual(initialResidual); - - context.GetOperator().Mult( - densityVariation, enthalpyVariation, displacementVariation, - initialAction + mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState ); - mfem::Vector changedDensity(density); - changedDensity.Add(0.025, densityVariation); + mfem::Vector density = reduce(maps.density, make_density(f, 0.17)); - mfem::Vector changedEnthalpy(enthalpy); - changedEnthalpy.Add(0.015, enthalpyVariation); + mfem::Vector enthalpy = reduce(maps.enthalpy, make_enthalpy(f, 0.29)); - const mfem::Vector deformedDisplacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector initialDisplacement = + reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.35)); - context.Prepare( - changedDensity, changedEnthalpy, deformedDisplacement, revisions - ); + /* + * A second smooth, orientation-preserving geometry. + * + * Do not manufacture a new geometry by perturbing an arbitrary H1 + * coefficient. A modest change in one high-order displacement DOF can + * correspond to a very large local displacement gradient and can invert + * an element. + */ + const mfem::Vector changedDisplacement = + reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.85)); - mfem::Vector unchangedResidual; - mfem::Vector unchangedAction; + mfem::Vector displacement(initialDisplacement); - context.BuildResidual(unchangedResidual); + auto dependencies = make_dependencies(); - context.GetOperator().Mult( - densityVariation, enthalpyVariation, displacementVariation, - unchangedAction - ); + const auto initialReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + const auto &context = preparedOperator.GetContext(); + + REQUIRE(preparedOperator.IsPrepared()); + + REQUIRE(context.IsPrepared()); + + CHECK(context.MatchesDependencies(dependencies)); + + CHECK(context.GetDependencies() == dependencies); + + CHECK(initialReport.contextReport.preparedStaticDependencies); + + CHECK(initialReport.contextReport.preparedGeometryState); + + CHECK(initialReport.contextReport.preparedBaseState); + + CHECK(initialReport.contextReport.updatedDensity); + + CHECK(initialReport.contextReport.updatedEnthalpy); + + CHECK(initialReport.contextReport.updatedDisplacement); + + CHECK(initialReport.preparedElementData); + + CHECK(initialReport.DidAnyWork()); + + CHECK(preparedOperator.GetPreparationCount() == 1); + + const mfem::Vector frozenDensity = context.GetBaseDensity(); + + const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpy(); + + const mfem::Vector frozenDisplacement = context.GetDisplacement(); + + /* + * Modify all three candidate states without updating their dependency + * stamps. + * + * The context must continue exposing the previously frozen state. + */ + density(0) += 0.25; + enthalpy(0) -= 0.18; + displacement = changedDisplacement; + + const auto repeatedReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + CHECK_FALSE(repeatedReport.DidAnyWork()); + + CHECK_FALSE(repeatedReport.contextReport.updatedDensity); + + CHECK_FALSE(repeatedReport.contextReport.updatedEnthalpy); + + CHECK_FALSE(repeatedReport.contextReport.updatedDisplacement); + + CHECK_FALSE(repeatedReport.preparedElementData); + + CHECK(preparedOperator.GetPreparationCount() == 1); const MPI_Comm communicator = f.mesh->GetComm(); - CHECK(context.GetPreparationCount() == 1); + CHECK(relative_error(context.GetBaseDensity(), frozenDensity, communicator) == 0.0); - CHECK( - gravity_prepared_test_utils::relative_error( - unchangedResidual, initialResidual, communicator - ) < 5.0e-15 + CHECK(relative_error(context.GetBaseEnthalpy(), frozenEnthalpy, communicator) == 0.0); + + CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0); + + /* + * Density invalidation. + * + * Geometry remains frozen because the displacement dependency did not + * change. + */ + ++dependencies.density.revision; + + const auto densityReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + CHECK_FALSE(densityReport.contextReport.preparedStaticDependencies); + + CHECK_FALSE(densityReport.contextReport.preparedGeometryState); + + CHECK(densityReport.contextReport.preparedBaseState); + + CHECK(densityReport.contextReport.updatedDensity); + + CHECK_FALSE(densityReport.contextReport.updatedEnthalpy); + + CHECK_FALSE(densityReport.contextReport.updatedDisplacement); + + CHECK(densityReport.preparedElementData); + + CHECK(context.GetBaseDensity()(0) == density(0)); + + /* + * The candidate displacement has changed, but because its revision has + * not changed the context must still retain the original geometry. + */ + CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0); + + /* + * Enthalpy invalidation. + */ + ++dependencies.enthalpy.revision; + + const auto enthalpyReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + CHECK_FALSE(enthalpyReport.contextReport.preparedStaticDependencies); + + CHECK_FALSE(enthalpyReport.contextReport.preparedGeometryState); + + CHECK(enthalpyReport.contextReport.preparedBaseState); + + CHECK_FALSE(enthalpyReport.contextReport.updatedDensity); + + CHECK(enthalpyReport.contextReport.updatedEnthalpy); + + CHECK_FALSE(enthalpyReport.contextReport.updatedDisplacement); + + CHECK(enthalpyReport.preparedElementData); + + CHECK(context.GetBaseEnthalpy()(0) == enthalpy(0)); + + CHECK(relative_error(context.GetDisplacement(), frozenDisplacement, communicator) == 0.0); + + /* + * Displacement invalidation. + * + * The changed geometry is now intentionally accepted. Because it came + * from the smooth test displacement projection rather than an arbitrary + * single H1 coefficient mutation, it remains a valid mapping. + */ + ++dependencies.displacement.revision; + + const auto displacementReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + CHECK_FALSE(displacementReport.contextReport.preparedStaticDependencies); + + CHECK(displacementReport.contextReport.preparedGeometryState); + + CHECK(displacementReport.contextReport.preparedBaseState); + + CHECK_FALSE(displacementReport.contextReport.updatedDensity); + + CHECK_FALSE(displacementReport.contextReport.updatedEnthalpy); + + CHECK(displacementReport.contextReport.updatedDisplacement); + + CHECK(displacementReport.preparedElementData); + + CHECK(relative_error(context.GetDisplacement(), changedDisplacement, communicator) == 0.0); + + /* + * Discretization invalidates everything. + */ + ++dependencies.discretization.revision; + + const auto discretizationReport = + preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + + CHECK(discretizationReport.contextReport.preparedStaticDependencies); + + CHECK(discretizationReport.contextReport.preparedGeometryState); + + CHECK(discretizationReport.contextReport.preparedBaseState); + + CHECK(discretizationReport.contextReport.updatedDensity); + + CHECK(discretizationReport.contextReport.updatedEnthalpy); + + CHECK(discretizationReport.contextReport.updatedDisplacement); + + CHECK(discretizationReport.preparedElementData); + + const auto finalStatistics = preparedOperator.GetContextPreparationStatistics(); + + CHECK(finalStatistics.staticPreparations == 2); + + CHECK(finalStatistics.geometryPreparations == 3); + + CHECK(finalStatistics.baseStatePreparations == 5); + + CHECK(preparedOperator.GetPreparationCount() == 5); +} + +TEST_CASE( + "Barotropic Closure Context Uses Identity And Revision For Every Dependency", + tags::barotrope &tags::closure &tags::contexts &tags::prepared &tags::field &tags::unit +) { + using namespace barotropic_closure_context_test_utils; + + 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 ); - CHECK( - gravity_prepared_test_utils::relative_error( - unchangedAction, initialAction, communicator - ) < 5.0e-15 - ); + mfem::Vector density = reduce(maps.density, make_density(f, 0.41)); + mfem::Vector enthalpy = reduce(maps.enthalpy, make_enthalpy(f, 0.53)); + mfem::Vector displacement = reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, 0.60)); - ++revisions.density; - ++revisions.enthalpy; - ++revisions.displacement; + auto dependencies = make_dependencies(); + preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); - context.Prepare( - changedDensity, changedEnthalpy, deformedDisplacement, revisions - ); + const mfem::Vector frozenDensity = preparedOperator.GetContext().GetBaseDensity(); + density(0) += 0.19; - mfem::Vector preparedResidual; - mfem::Vector preparedAction; + const auto sameStampReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + CHECK_FALSE(sameStampReport.DidAnyWork()); + CHECK(preparedOperator.GetContext().GetBaseDensity()(0) == frozenDensity(0)); - context.BuildResidual(preparedResidual); + ++dependencies.density.identity; + ++dependencies.density.revision; - context.GetOperator().Mult( - densityVariation, enthalpyVariation, displacementVariation, - preparedAction - ); + const auto newIdentityReport = preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + CHECK_FALSE(newIdentityReport.contextReport.preparedStaticDependencies); + CHECK_FALSE(newIdentityReport.contextReport.preparedGeometryState); + CHECK(newIdentityReport.contextReport.preparedBaseState); + CHECK(newIdentityReport.contextReport.updatedDensity); + CHECK(preparedOperator.GetContext().GetBaseDensity()(0) == density(0)); + CHECK(preparedOperator.GetContext().MatchesDependencies(dependencies)); - mfem::Vector referenceResidual; - mfem::Vector referenceDensityAction; - mfem::Vector referenceEnthalpyAction; - mfem::Vector referenceDisplacementAction; + ++dependencies.displacement.identity; + ++dependencies.displacement.revision; - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, changedDensity, changedEnthalpy, - deformedDisplacement, referenceResidual - ); + const auto displacementIdentityReport = + preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + CHECK_FALSE(displacementIdentityReport.contextReport.preparedStaticDependencies); + CHECK(displacementIdentityReport.contextReport.preparedGeometryState); + CHECK(displacementIdentityReport.contextReport.preparedBaseState); + CHECK(displacementIdentityReport.contextReport.updatedDisplacement); - mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, - deformedDisplacement, referenceDensityAction - ); + ++dependencies.discretization.identity; + ++dependencies.discretization.revision; - mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action( - f, *f.domainMapperStateless, barotrope, changedEnthalpy, - enthalpyVariation, deformedDisplacement, referenceEnthalpyAction - ); - - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, changedDensity, - changedEnthalpy, deformedDisplacement, displacementVariation, - referenceDisplacementAction - ); - - mfem::Vector referenceAction(referenceDensityAction); - referenceAction += referenceEnthalpyAction; - referenceAction += referenceDisplacementAction; - const double residualError = gravity_prepared_test_utils::relative_error( - preparedResidual, referenceResidual, communicator - ); - - const double actionError = gravity_prepared_test_utils::relative_error( - preparedAction, referenceAction, communicator - ); - - const double residualChange = gravity_prepared_test_utils::relative_error( - preparedResidual, initialResidual, communicator - ); - - const double actionChange = gravity_prepared_test_utils::relative_error( - preparedAction, initialAction, communicator - ); - - INFO("Prepared-context residual error = " << residualError); - - INFO("Prepared-context Jacobian error = " << actionError); - - INFO("Residual change after valid revision = " << residualChange); - - INFO("Jacobian change after valid revision = " << actionChange); - - CHECK(context.GetPreparationCount() == 2); - CHECK(residualError < 5.0e-12); - CHECK(actionError < 5.0e-12); - CHECK(residualChange > 1.0e-6); - CHECK(actionChange > 1.0e-6); -} \ No newline at end of file + const auto discretizationIdentityReport = + preparedOperator.Prepare(make_state(density, enthalpy, displacement), dependencies); + CHECK(discretizationIdentityReport.contextReport.preparedStaticDependencies); + CHECK(discretizationIdentityReport.contextReport.preparedGeometryState); + CHECK(discretizationIdentityReport.contextReport.preparedBaseState); +} diff --git a/tests/operators/contexts/gravity_field_context.cpp b/tests/operators/contexts/gravity_field_context.cpp index 10218ba..a7fa840 100644 --- a/tests/operators/contexts/gravity_field_context.cpp +++ b/tests/operators/contexts/gravity_field_context.cpp @@ -15,20 +15,13 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - gravity_context::GravityFieldLinearizationContext context( - f, *f.domainMapperStateless - ); + gravity_context::GravityFieldLinearizationContext context(f, *f.domainMapperStateless); - mfem::Vector density = prepared_test::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.11 - ); + mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.11); mfem::Vector displacement = prepared_test::make_displacement(f, 0.0); - mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.37 - ); - mfem::Vector gravity_potential = prepared_test::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.63 - ); + mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.37); + mfem::Vector gravity_potential = + prepared_test::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.63); gravity_context::GravityFieldRevisions revisions; @@ -43,8 +36,7 @@ TEST_CASE( REQUIRE_FALSE(context.IsPrepared()); - const gravity_context::GravityFieldPreparationReport initial_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport initial_report = context.Prepare(make_state(), revisions); REQUIRE(context.IsPrepared()); CHECK(initial_report.geometry.reconstructed_operators); @@ -55,41 +47,27 @@ TEST_CASE( CHECK(initial_report.updated_gravity_gradient); CHECK(initial_report.DidAnyWork()); - const auto initial_mass_preparations = - context.GetGeometryContext().GetMassOperator().GetPreparationCount(); - const auto initial_source_preparations = - context.GetGeometryContext().GetSourceOperator().GetPreparationCount(); + const auto initial_mass_preparations = context.GetGeometryContext().GetMassOperator().GetPreparationCount(); + const auto initial_source_preparations = context.GetGeometryContext().GetSourceOperator().GetPreparationCount(); - const gravity_context::GravityFieldPreparationReport repeated_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport repeated_report = context.Prepare(make_state(), revisions); CHECK_FALSE(repeated_report.DidAnyWork()); - CHECK( - context.GetGeometryContext().GetMassOperator().GetPreparationCount() == - initial_mass_preparations - ); - CHECK( - context.GetGeometryContext() - .GetSourceOperator() - .GetPreparationCount() == initial_source_preparations - ); + CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == initial_mass_preparations); + CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == initial_source_preparations); gravity_potential(0) += 0.25; ++revisions.gravity_potential.value; - const gravity_context::GravityFieldPreparationReport potential_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport potential_report = context.Prepare(make_state(), revisions); CHECK_FALSE(potential_report.DidAnyWork()); - CHECK( - context.GetRevisions().gravity_potential == revisions.gravity_potential - ); + CHECK(context.GetRevisions().gravity_potential == revisions.gravity_potential); density(0) += 0.5; ++revisions.density.value; - const gravity_context::GravityFieldPreparationReport density_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport density_report = context.Prepare(make_state(), revisions); CHECK(density_report.updated_density); CHECK_FALSE(density_report.updated_gravity_gradient); @@ -99,8 +77,7 @@ TEST_CASE( gravity_gradient(0) -= 0.4; ++revisions.gravity_gradient.value; - const gravity_context::GravityFieldPreparationReport gradient_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport gradient_report = context.Prepare(make_state(), revisions); CHECK_FALSE(gradient_report.updated_density); CHECK(gradient_report.updated_gravity_gradient); @@ -110,8 +87,7 @@ TEST_CASE( displacement = prepared_test::make_displacement(f, 1.0); ++revisions.displacement.value; - const gravity_context::GravityFieldPreparationReport displacement_report = - context.Prepare(make_state(), revisions); + const gravity_context::GravityFieldPreparationReport displacement_report = context.Prepare(make_state(), revisions); CHECK_FALSE(displacement_report.geometry.reconstructed_operators); CHECK(displacement_report.geometry.rebuilt_mass_operator); @@ -119,15 +95,8 @@ TEST_CASE( CHECK(displacement_report.geometry.refreshed_variation_state); CHECK_FALSE(displacement_report.updated_density); CHECK_FALSE(displacement_report.updated_gravity_gradient); - CHECK( - context.GetGeometryContext().GetMassOperator().GetPreparationCount() == - initial_mass_preparations + 1 - ); - CHECK( - context.GetGeometryContext() - .GetSourceOperator() - .GetPreparationCount() == initial_source_preparations + 1 - ); + CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == initial_mass_preparations + 1); + CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == initial_source_preparations + 1); ++revisions.discretization.value; @@ -139,15 +108,8 @@ TEST_CASE( CHECK(discretization_report.geometry.rebuilt_source_operator); CHECK(discretization_report.updated_density); CHECK(discretization_report.updated_gravity_gradient); - CHECK( - context.GetGeometryContext().GetMassOperator().GetPreparationCount() == - 1 - ); - CHECK( - context.GetGeometryContext() - .GetSourceOperator() - .GetPreparationCount() == 1 - ); + CHECK(context.GetGeometryContext().GetMassOperator().GetPreparationCount() == 1); + CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == 1); } TEST_CASE( @@ -157,20 +119,13 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - gravity_context::GravityFieldLinearizationContext context( - f, *f.domainMapperStateless - ); + gravity_context::GravityFieldLinearizationContext context(f, *f.domainMapperStateless); - mfem::Vector density = prepared_test::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.13 - ); + mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.13); mfem::Vector displacement = prepared_test::make_displacement(f, 0.4); - mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.47 - ); - mfem::Vector gravity_potential = prepared_test::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.71 - ); + mfem::Vector gravity_gradient = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.47); + mfem::Vector gravity_potential = + prepared_test::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.71); gravity_context::GravityFieldRevisions revisions; @@ -182,9 +137,8 @@ TEST_CASE( revisions ); - const mfem::Vector frozen_density = context.GetDensity(); - const mfem::Vector frozen_displacement = - context.GetGeometryContext().GetDisplacement(); + const mfem::Vector frozen_density = context.GetDensity(); + const mfem::Vector frozen_displacement = context.GetGeometryContext().GetDisplacement(); const mfem::Vector frozen_gravity_gradient = context.GetGravityGradient(); density = 0.0; @@ -192,49 +146,36 @@ TEST_CASE( gravity_gradient = 0.0; gravity_potential = 0.0; + CHECK(prepared_test::relative_error(context.GetDensity(), frozen_density, f.mesh->GetComm()) == 0.0); CHECK( prepared_test::relative_error( - context.GetDensity(), frozen_density, f.mesh->GetComm() + context.GetGeometryContext().GetDisplacement(), frozen_displacement, f.displacementFes->GetComm() ) == 0.0 ); CHECK( prepared_test::relative_error( - context.GetGeometryContext().GetDisplacement(), frozen_displacement, - f.displacementFes->GetComm() - ) == 0.0 - ); - CHECK( - prepared_test::relative_error( - context.GetGravityGradient(), frozen_gravity_gradient, - f.gravityFluxFes->GetComm() + context.GetGravityGradient(), frozen_gravity_gradient, f.gravityFluxFes->GetComm() ) == 0.0 ); - const gravity_context::GravityFieldPreparationReport - unchanged_revision_report = context.Prepare( - {.density = density, - .displacement = displacement, - .gravity_gradient = gravity_gradient, - .gravity_potential = gravity_potential}, - revisions - ); + const gravity_context::GravityFieldPreparationReport unchanged_revision_report = context.Prepare( + {.density = density, + .displacement = displacement, + .gravity_gradient = gravity_gradient, + .gravity_potential = gravity_potential}, + revisions + ); CHECK_FALSE(unchanged_revision_report.DidAnyWork()); + CHECK(prepared_test::relative_error(context.GetDensity(), frozen_density, f.mesh->GetComm()) == 0.0); CHECK( prepared_test::relative_error( - context.GetDensity(), frozen_density, f.mesh->GetComm() + context.GetGeometryContext().GetDisplacement(), frozen_displacement, f.displacementFes->GetComm() ) == 0.0 ); CHECK( prepared_test::relative_error( - context.GetGeometryContext().GetDisplacement(), frozen_displacement, - f.displacementFes->GetComm() - ) == 0.0 - ); - CHECK( - prepared_test::relative_error( - context.GetGravityGradient(), frozen_gravity_gradient, - f.gravityFluxFes->GetComm() + context.GetGravityGradient(), frozen_gravity_gradient, f.gravityFluxFes->GetComm() ) == 0.0 ); } @@ -246,21 +187,13 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - gravity_context::GravityFieldGeometryContext first_context( - f, *f.domainMapperStateless - ); - gravity_context::GravityFieldGeometryContext second_context( - f, *f.domainMapperStateless - ); + gravity_context::GravityFieldGeometryContext first_context(f, *f.domainMapperStateless); + gravity_context::GravityFieldGeometryContext second_context(f, *f.domainMapperStateless); - const mfem::Vector first_displacement = - prepared_test::make_displacement(f, 0.0); - const mfem::Vector second_displacement = - prepared_test::make_displacement(f, 1.0); + const mfem::Vector first_displacement = prepared_test::make_displacement(f, 0.0); + const mfem::Vector second_displacement = prepared_test::make_displacement(f, 1.0); const mfem::Vector gravity_gradient = - prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.35 - ); + prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.35); first_context.Prepare(first_displacement, {.value = 0}, {.value = 0}); second_context.Prepare(second_displacement, {.value = 0}, {.value = 0}); @@ -270,36 +203,21 @@ TEST_CASE( mfem::Vector second_action_after; first_context.GetMassOperator().Mult(gravity_gradient, first_action); - second_context.GetMassOperator().Mult( - gravity_gradient, second_action_before - ); + second_context.GetMassOperator().Mult(gravity_gradient, second_action_before); - const mfem::Vector updated_first_displacement = - prepared_test::make_displacement(f, 0.6); - first_context.Prepare( - updated_first_displacement, {.value = 0}, {.value = 1} - ); + const mfem::Vector updated_first_displacement = prepared_test::make_displacement(f, 0.6); + first_context.Prepare(updated_first_displacement, {.value = 0}, {.value = 1}); - second_context.GetMassOperator().Mult( - gravity_gradient, second_action_after - ); + second_context.GetMassOperator().Mult(gravity_gradient, second_action_after); - const MPI_Comm communicator = f.gravityFluxFes->GetComm(); - const double independent_context_error = prepared_test::relative_error( - second_action_after, second_action_before, communicator - ); - const double distinct_geometry_difference = prepared_test::relative_error( - first_action, second_action_before, communicator - ); + const MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const double independent_context_error = + prepared_test::relative_error(second_action_after, second_action_before, communicator); + const double distinct_geometry_difference = + prepared_test::relative_error(first_action, second_action_before, communicator); - INFO( - "Second-context change after preparing first context = " - << independent_context_error - ); - INFO( - "Difference between independently prepared geometries = " - << distinct_geometry_difference - ); + INFO("Second-context change after preparing first context = " << independent_context_error); + INFO("Difference between independently prepared geometries = " << distinct_geometry_difference); CHECK(independent_context_error < 2.0e-14); CHECK(distinct_geometry_difference > 1.0e-5); diff --git a/tests/operators/contexts/hydrostatic_equilibrium_context.cpp b/tests/operators/contexts/hydrostatic_equilibrium_context.cpp index ce8a580..2c2cdec 100644 --- a/tests/operators/contexts/hydrostatic_equilibrium_context.cpp +++ b/tests/operators/contexts/hydrostatic_equilibrium_context.cpp @@ -5,9 +5,7 @@ import mean_field; import test_helpers; namespace hydrostatic_context_test_utils { - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 101, .revision = 2}, .enthalpy = {.identity = 103, .revision = 3}, @@ -18,8 +16,7 @@ namespace hydrostatic_context_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement, @@ -33,10 +30,7 @@ namespace hydrostatic_context_test_utils { }; } - void check_base_only( - const mean_field::operators::context::hydrostatic:: - HydrostaticPreparationReport &report - ) { + void check_base_only(const mean_field::operators::context::hydrostatic::HydrostaticPreparationReport &report) { CHECK_FALSE(report.preparedStaticDependencies); CHECK_FALSE(report.preparedGeometryState); CHECK_FALSE(report.preparedRotationDependencies); @@ -48,32 +42,23 @@ TEST_CASE( "Hydrostatic Context Applies Selective Invalidation", tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext - context(f, *f.domainMapperStateless); + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext context(f, *f.domainMapperStateless); - mfem::Vector enthalpy = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.17 - ); + mfem::Vector enthalpy = gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.17); mfem::Vector gravityPotential = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.31 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.31); - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.35); + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.35); - double bernoulliConstant = 0.73; + double bernoulliConstant = 0.73; - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies dependencies = - hydrostatic_context_test_utils::make_dependencies(); + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies dependencies = + hydrostatic_context_test_utils::make_dependencies(); CHECK_FALSE(context.IsPrepared()); CHECK_FALSE(context.MatchesDependencies(dependencies)); @@ -86,9 +71,7 @@ TEST_CASE( CHECK(initialStatistics.baseStatePreparations == 0); const auto initialReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -106,14 +89,13 @@ TEST_CASE( CHECK(initialReport.updatedBernoulliConstant); CHECK(initialReport.DidAnyWork()); - const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue(); + const mfem::Vector frozenEnthalpy = context.GetBaseEnthalpyTrue(); - const mfem::Vector frozenGravityPotential = - context.GetBaseGravityPotentialTrue(); + const mfem::Vector frozenGravityPotential = context.GetBaseGravityPotentialTrue(); - const mfem::Vector frozenDisplacement = context.GetDisplacementTrue(); + const mfem::Vector frozenDisplacement = context.GetDisplacementTrue(); - const double frozenBernoulliConstant = context.GetBernoulliConstant(); + const double frozenBernoulliConstant = context.GetBernoulliConstant(); enthalpy(0) += 0.125; gravityPotential(0) -= 0.075; @@ -121,9 +103,7 @@ TEST_CASE( bernoulliConstant += 0.20; const auto repeatedReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -136,22 +116,18 @@ TEST_CASE( const MPI_Comm communicator = f.mesh->GetComm(); CHECK( - gravity_prepared_test_utils::relative_error( - context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator - ) == 0.0 + gravity_prepared_test_utils::relative_error(context.GetBaseEnthalpyTrue(), frozenEnthalpy, communicator) == 0.0 ); CHECK( gravity_prepared_test_utils::relative_error( - context.GetBaseGravityPotentialTrue(), frozenGravityPotential, - communicator + context.GetBaseGravityPotentialTrue(), frozenGravityPotential, communicator ) == 0.0 ); CHECK( - gravity_prepared_test_utils::relative_error( - context.GetDisplacementTrue(), frozenDisplacement, communicator - ) == 0.0 + gravity_prepared_test_utils::relative_error(context.GetDisplacementTrue(), frozenDisplacement, communicator) == + 0.0 ); CHECK(context.GetBernoulliConstant() == frozenBernoulliConstant); @@ -159,9 +135,7 @@ TEST_CASE( ++dependencies.enthalpy.revision; const auto enthalpyReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -176,9 +150,7 @@ TEST_CASE( ++dependencies.gravityPotential.revision; const auto gravityPotentialReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -194,9 +166,7 @@ TEST_CASE( ++dependencies.bernoulliConstant.revision; const auto bernoulliReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -212,9 +182,7 @@ TEST_CASE( ++dependencies.rotation.revision; const auto rotationReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -230,9 +198,7 @@ TEST_CASE( ++dependencies.displacement.revision; const auto displacementReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -251,9 +217,7 @@ TEST_CASE( ++dependencies.discretization.revision; const auto discretizationReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -278,32 +242,23 @@ TEST_CASE( "Hydrostatic Context Uses Identity In Every Dependency", tags::barotrope &tags::contexts &tags::hydro &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext - context(f, *f.domainMapperStateless); + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumContext context(f, *f.domainMapperStateless); - mfem::Vector enthalpy = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.23 - ); + mfem::Vector enthalpy = gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23); const mfem::Vector gravityPotential = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.41 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.41); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.60); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.60); constexpr double bernoulliConstant = 0.81; - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies dependencies = - hydrostatic_context_test_utils::make_dependencies(); + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies dependencies = + hydrostatic_context_test_utils::make_dependencies(); const auto preparedEnthalpyDependency = dependencies.enthalpy; @@ -319,9 +274,7 @@ TEST_CASE( CHECK(resetNewIdentity.CanFollow(preparedEnthalpyDependency)); context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -330,9 +283,7 @@ TEST_CASE( enthalpy(0) += 0.33; const auto sameStampReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -343,9 +294,7 @@ TEST_CASE( dependencies.enthalpy.revision = 0; const auto newIdentityReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); @@ -359,9 +308,7 @@ TEST_CASE( dependencies.rotation.revision = 0; const auto newRotationIdentityReport = context.Prepare( - hydrostatic_context_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + hydrostatic_context_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies ); diff --git a/tests/operators/contexts/pressure_force_context.cpp b/tests/operators/contexts/pressure_force_context.cpp new file mode 100644 index 0000000..10618f6 --- /dev/null +++ b/tests/operators/contexts/pressure_force_context.cpp @@ -0,0 +1,325 @@ +#include +#include +#include + +#include +#include + +import mean_field; +import test_helpers; + +namespace pressure_force_context_test_utils { + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + struct Maps final { + mean_field::field::FieldDofMap enthalpy; + mean_field::field::FieldDofMap displacement; + + explicit Maps(const mean_field::fem::FEM &f) + : enthalpy( + mean_field::field::make_field_dof_map< + mean_field::field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ), + displacement( + mean_field::field::make_field_dof_map< + mean_field::field::Displacement, + DomainSchema>(*f.displacementFes) + ) { + } + }; + + [[nodiscard]] + mfem::Vector make_enthalpy_true( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize()); + + for (int index = 0; index < enthalpy.Size(); ++index) { + const double position = static_cast(index + 1); + + enthalpy(index) = + 0.95 + 0.08 * std::sin(0.17 * position + phase) + 0.03 * std::cos(0.11 * position - 0.5 * phase); + } + + return enthalpy; + } + + [[nodiscard]] + double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const MPI_Comm communicator + ) { + MFEM_VERIFY( + left.Size() == right.Size(), "Cannot compare pressure-force context vectors with " + "different sizes." + ); + + mfem::Vector difference(left); + + difference -= right; + + const double scale = std::max( + {gravity_prepared_test_utils::global_norm(left, communicator), + gravity_prepared_test_utils::global_norm(right, communicator), + 100.0 * std::numeric_limits::epsilon()} + ); + + return gravity_prepared_test_utils::global_norm(difference, communicator) / scale; + } + + [[nodiscard]] + mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() { + return { + .discretization = {.identity = 101, .revision = 7}, + .enthalpy = {.identity = 103, .revision = 11}, + .displacement = {.identity = 107, .revision = 13} + }; + } +} // namespace pressure_force_context_test_utils + +TEST_CASE( + "Pressure Force Context Applies Selective Invalidation In FieldDof Coordinates", + tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit +) { + 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 pressure_force_context_test_utils::Maps maps(f); + + mean_field::operators::context::pressure_force::PressureForceLinearizationContext context( + f, *f.domainMapperStateless, maps.enthalpy, maps.displacement + ); + + mfem::Vector enthalpy = maps.enthalpy.gather(pressure_force_context_test_utils::make_enthalpy_true(f, 0.23)); + + const mfem::Vector initialDisplacement = + maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.41)); + + const mfem::Vector changedDisplacement = + maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.79)); + + mfem::Vector displacement(initialDisplacement); + + auto dependencies = pressure_force_context_test_utils::make_dependencies(); + + const mean_field::operators::context::pressure_force::PressureForceStateView state{ + .enthalpy = enthalpy, .displacement = displacement + }; + + CHECK_FALSE(context.IsPrepared()); + + const auto initialReport = context.Prepare(state, dependencies); + + REQUIRE(context.IsPrepared()); + + CHECK(context.MatchesDependencies(dependencies)); + + CHECK(initialReport.DidAnyWork()); + + CHECK(initialReport.preparedStaticDependencies); + + CHECK(initialReport.preparedGeometryState); + + CHECK(initialReport.preparedMaterialState); + + CHECK(initialReport.updatedEnthalpy); + + CHECK(initialReport.updatedDisplacement); + + REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size()); + + CHECK(maps.displacement.is_identity()); + + CHECK(context.GetBaseEnthalpy().Size() == maps.enthalpy.reduced_size()); + + CHECK(context.GetDisplacement().Size() == maps.displacement.reduced_size()); + + const auto unchangedReport = context.Prepare(state, dependencies); + + CHECK_FALSE(unchangedReport.DidAnyWork()); + + const mfem::Vector frozenEnthalpy(context.GetBaseEnthalpy()); + + const mfem::Vector frozenDisplacement(context.GetDisplacement()); + + enthalpy(0) += 0.125; + + displacement = changedDisplacement; + + const auto unstampedReport = context.Prepare(state, dependencies); + + CHECK_FALSE(unstampedReport.DidAnyWork()); + + CHECK( + pressure_force_context_test_utils::relative_difference( + context.GetBaseEnthalpy(), frozenEnthalpy, f.mesh->GetComm() + ) == 0.0 + ); + + CHECK( + pressure_force_context_test_utils::relative_difference( + context.GetDisplacement(), frozenDisplacement, f.mesh->GetComm() + ) == 0.0 + ); + + ++dependencies.enthalpy.revision; + + const auto enthalpyReport = context.Prepare(state, dependencies); + + CHECK(enthalpyReport.DidAnyWork()); + + CHECK_FALSE(enthalpyReport.preparedStaticDependencies); + + CHECK_FALSE(enthalpyReport.preparedGeometryState); + + CHECK(enthalpyReport.preparedMaterialState); + + CHECK(enthalpyReport.updatedEnthalpy); + + CHECK_FALSE(enthalpyReport.updatedDisplacement); + + CHECK(context.GetBaseEnthalpy()(0) == enthalpy(0)); + + CHECK( + pressure_force_context_test_utils::relative_difference( + context.GetDisplacement(), frozenDisplacement, f.mesh->GetComm() + ) == 0.0 + ); + + ++dependencies.displacement.revision; + + const auto displacementReport = context.Prepare(state, dependencies); + + CHECK(displacementReport.DidAnyWork()); + + CHECK_FALSE(displacementReport.preparedStaticDependencies); + + CHECK(displacementReport.preparedGeometryState); + + CHECK(displacementReport.preparedMaterialState); + + CHECK_FALSE(displacementReport.updatedEnthalpy); + + CHECK(displacementReport.updatedDisplacement); + + CHECK( + pressure_force_context_test_utils::relative_difference( + context.GetDisplacement(), changedDisplacement, f.mesh->GetComm() + ) == 0.0 + ); + + ++dependencies.discretization.revision; + + const auto discretizationReport = context.Prepare(state, dependencies); + + CHECK(discretizationReport.preparedStaticDependencies); + + CHECK(discretizationReport.preparedGeometryState); + + CHECK(discretizationReport.preparedMaterialState); + + CHECK(discretizationReport.updatedEnthalpy); + + CHECK(discretizationReport.updatedDisplacement); + + const auto &statistics = context.GetPreparationStatistics(); + + CHECK(statistics.staticPreparations == 2); + + CHECK(statistics.geometryPreparations == 3); + + CHECK(statistics.materialPreparations == 4); +} + +TEST_CASE( + "Pressure Force Context Uses Identity And Revision In Every Dependency", + tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit +) { + 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 pressure_force_context_test_utils::Maps maps(f); + + mean_field::operators::context::pressure_force::PressureForceLinearizationContext context( + f, *f.domainMapperStateless, maps.enthalpy, maps.displacement + ); + + const mfem::Vector enthalpy = maps.enthalpy.gather(pressure_force_context_test_utils::make_enthalpy_true(f, 0.61)); + + const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.73)); + + const mean_field::operators::context::pressure_force::PressureForceStateView state{ + .enthalpy = enthalpy, .displacement = displacement + }; + + auto dependencies = pressure_force_context_test_utils::make_dependencies(); + + context.Prepare(state, dependencies); + + ++dependencies.enthalpy.identity; + + dependencies.enthalpy.revision = 0; + + const auto enthalpyIdentityReport = context.Prepare(state, dependencies); + + CHECK_FALSE(enthalpyIdentityReport.preparedStaticDependencies); + + CHECK_FALSE(enthalpyIdentityReport.preparedGeometryState); + + CHECK(enthalpyIdentityReport.preparedMaterialState); + + CHECK(enthalpyIdentityReport.updatedEnthalpy); + + CHECK_FALSE(enthalpyIdentityReport.updatedDisplacement); + + ++dependencies.displacement.identity; + + dependencies.displacement.revision = 0; + + const auto displacementIdentityReport = context.Prepare(state, dependencies); + + CHECK_FALSE(displacementIdentityReport.preparedStaticDependencies); + + CHECK(displacementIdentityReport.preparedGeometryState); + + CHECK(displacementIdentityReport.preparedMaterialState); + + CHECK_FALSE(displacementIdentityReport.updatedEnthalpy); + + CHECK(displacementIdentityReport.updatedDisplacement); + + ++dependencies.discretization.identity; + + dependencies.discretization.revision = 0; + + const auto discretizationIdentityReport = context.Prepare(state, dependencies); + + CHECK(discretizationIdentityReport.preparedStaticDependencies); + + CHECK(discretizationIdentityReport.preparedGeometryState); + + CHECK(discretizationIdentityReport.preparedMaterialState); + + CHECK(discretizationIdentityReport.updatedEnthalpy); + + CHECK(discretizationIdentityReport.updatedDisplacement); + + CHECK(context.MatchesDependencies(dependencies)); + + const auto &statistics = context.GetPreparationStatistics(); + + CHECK(statistics.staticPreparations == 2); + + CHECK(statistics.geometryPreparations == 3); + + CHECK(statistics.materialPreparations == 4); +} \ No newline at end of file diff --git a/tests/operators/contexts/rotation_displacement_force_context.cpp b/tests/operators/contexts/rotation_displacement_force_context.cpp new file mode 100644 index 0000000..b20c74d --- /dev/null +++ b/tests/operators/contexts/rotation_displacement_force_context.cpp @@ -0,0 +1,181 @@ +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace rotational_displacement_force_context_test_utils { + using Context = + mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext; + + using Dependencies = + mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies; + + [[nodiscard]] Dependencies make_dependencies() { + return { + .discretization = {.identity = 101, .revision = 3}, + .density = {.identity = 103, .revision = 5}, + .displacement = {.identity = 107, .revision = 7}, + .rotation = {.identity = 109, .revision = 11} + }; + } + + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double offset + ) { + mfem::ParGridFunction density(f.densityFes.get()); + + mfem::FunctionCoefficient coefficient([offset](const mfem::Vector &position) { + return offset + 0.04 * position(0) - 0.03 * position(1) + 0.02 * position(2); + }); + + density.ProjectCoefficient(coefficient); + + mfem::Vector densityTrue; + density.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right + ) { + mfem::Vector difference(left); + difference -= right; + + return difference.Norml2() / std::max(right.Norml2(), 1.0e-30); + } +} // namespace rotational_displacement_force_context_test_utils + +TEST_CASE( + "Rotational Displacement Force Context Applies Selective Invalidation", + tags::centrifugal &tags::contexts &tags::unit +) { + 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()); + + mfem::Vector density = rotational_displacement_force_context_test_utils::make_density(f, 0.83); + + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.47); + + auto dependencies = rotational_displacement_force_context_test_utils::make_dependencies(); + + rotational_displacement_force_context_test_utils::Context context(f, *f.domainMapperStateless); + + const auto initialReport = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + REQUIRE(context.IsPrepared()); + CHECK(context.MatchesDependencies(dependencies)); + CHECK(initialReport.preparedStaticDependencies); + CHECK(initialReport.preparedGeometryState); + CHECK(initialReport.preparedRotationDependencies); + CHECK(initialReport.preparedBaseState); + CHECK(initialReport.updatedDensity); + CHECK(initialReport.updatedDisplacement); + + CHECK( + rotational_displacement_force_context_test_utils::relative_difference(context.GetBaseDensityTrue(), density) < + 1.0e-14 + ); + + CHECK( + rotational_displacement_force_context_test_utils::relative_difference( + context.GetDisplacementTrue(), displacement + ) < 1.0e-14 + ); + + const auto unchangedReport = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + CHECK_FALSE(unchangedReport.DidAnyWork()); + + density = rotational_displacement_force_context_test_utils::make_density(f, 1.17); + + ++dependencies.density.revision; + + const auto densityReport = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + CHECK_FALSE(densityReport.preparedStaticDependencies); + CHECK_FALSE(densityReport.preparedGeometryState); + CHECK_FALSE(densityReport.preparedRotationDependencies); + CHECK(densityReport.preparedBaseState); + CHECK(densityReport.updatedDensity); + CHECK_FALSE(densityReport.updatedDisplacement); + + const mfem::Vector densityAfterDensityRevision(context.GetBaseDensityTrue()); + + displacement = gravity_prepared_test_utils::make_displacement(f, 0.81); + + ++dependencies.displacement.revision; + + const auto displacementReport = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + CHECK_FALSE(displacementReport.preparedStaticDependencies); + CHECK(displacementReport.preparedGeometryState); + CHECK_FALSE(displacementReport.preparedRotationDependencies); + CHECK(displacementReport.preparedBaseState); + CHECK_FALSE(displacementReport.updatedDensity); + CHECK(displacementReport.updatedDisplacement); + + CHECK( + rotational_displacement_force_context_test_utils::relative_difference( + context.GetBaseDensityTrue(), densityAfterDensityRevision + ) < 1.0e-14 + ); + + ++dependencies.rotation.revision; + + const auto rotationReport = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + CHECK_FALSE(rotationReport.preparedStaticDependencies); + CHECK_FALSE(rotationReport.preparedGeometryState); + CHECK(rotationReport.preparedRotationDependencies); + CHECK(rotationReport.preparedBaseState); + CHECK_FALSE(rotationReport.updatedDensity); + CHECK_FALSE(rotationReport.updatedDisplacement); + + const auto statistics = context.GetPreparationStatistics(); + + CHECK(statistics.staticPreparations == 1); + CHECK(statistics.geometryPreparations == 2); + CHECK(statistics.rotationPreparations == 2); + CHECK(statistics.baseStatePreparations == 4); +} + +TEST_CASE( + "Rotational Displacement Force Context Treats New Identities As New " + "Dependency Streams", + tags::centrifugal &tags::contexts &tags::unit +) { + 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 mfem::Vector density = rotational_displacement_force_context_test_utils::make_density(f, 0.91); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.39); + + auto dependencies = rotational_displacement_force_context_test_utils::make_dependencies(); + + rotational_displacement_force_context_test_utils::Context context(f, *f.domainMapperStateless); + + context.Prepare({.density = density, .displacement = displacement}, dependencies); + + dependencies.density.identity += 1000; + dependencies.density.revision = 0; + + const auto report = context.Prepare({.density = density, .displacement = displacement}, dependencies); + + CHECK(report.preparedBaseState); + CHECK(report.updatedDensity); + CHECK_FALSE(report.preparedGeometryState); + CHECK_FALSE(report.preparedRotationDependencies); +} diff --git a/tests/operators/gravity_displacement_force.cpp b/tests/operators/gravity_displacement_force.cpp new file mode 100644 index 0000000..9f40ffb --- /dev/null +++ b/tests/operators/gravity_displacement_force.cpp @@ -0,0 +1,710 @@ +#include +#include +#include +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace gravity_displacement_force_test_utils { + using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto gravityGradientValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); + + constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.gradient_term + ); + + constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.poisson_term + ); + + constexpr auto densityResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + [[nodiscard]] mean_field::operators::GravityDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) { + const std::array valueSizes{ + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + const std::array residualSizes{ + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(), + f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + return {valueSizes, residualSizes}; + } + + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction densityField(f.densityFes.get()); + + mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) { + return 0.82 + 0.07 * std::sin(0.8 * position(0) + phase) + + 0.05 * std::cos(0.6 * position(1) - 0.3 * phase) + 0.03 * position(2) * position(2); + }); + + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector make_density_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction densityField(f.densityFes.get()); + + mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) { + return 0.19 * std::sin(0.9 * position(0) + phase) - 0.13 * std::cos(0.7 * position(1) - phase) + + 0.08 * position(2); + }); + + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector make_gravity_gradient( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); + + auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + + value(0) = 0.31 + 0.08 * position(0) + 0.03 * phase * position(1); + + value(1) = -0.17 + 0.06 * position(1) - 0.02 * phase * position(2); + + value(2) = 0.23 - 0.05 * position(2) + 0.025 * phase * position(0); + }; + + mfem::VectorFunctionCoefficient gravityCoefficient(3, gravityFunction); + + gravityField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector gravityTrue; + gravityField.GetTrueDofs(gravityTrue); + return gravityTrue; + } + + [[nodiscard]] mfem::Vector make_gravity_gradient_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); + + auto gravityFunction = [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + + value(0) = 0.14 * std::sin(position(0) + phase) + 0.03 * position(1); + + value(1) = -0.11 * std::cos(position(1) - phase) + 0.04 * position(2); + + value(2) = 0.09 * std::sin(position(2) + 0.5 * phase) - 0.02 * position(0); + }; + + mfem::VectorFunctionCoefficient gravityCoefficient(3, gravityFunction); + + gravityField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector gravityTrue; + gravityField.GetTrueDofs(gravityTrue); + return gravityTrue; + } + + [[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) { + mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.83); + + const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.29); + + direction -= second; + return direction; + } + + [[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) { + mfem::ParGridFunction densityField(f.densityFes.get()); + densityField = 0.0; + + const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); + + mfem::Array densityDofs; + int localVacuumElements = 0; + + for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); + + REQUIRE(transformation != nullptr); + + if (transformation->Attribute != vacuumAttribute) { + continue; + } + + f.densityFes->GetElementDofs(elementId, densityDofs); + + mfem::Vector elementDensity(densityDofs.Size()); + elementDensity = 1.0; + densityField.SetSubVector(densityDofs, elementDensity); + ++localVacuumElements; + } + + int globalVacuumElements = 0; + MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm()); + + REQUIRE(globalVacuumElements > 0); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_revisions() { + return { + .discretization = {.value = 3}, + .displacement = {.value = 5}, + .density = {.value = 7}, + .gravity_gradient = {.value = 11}, + .gravity_potential = {.value = 13} + }; + } + + void prepare_gravity_context( + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context, + const mfem::Vector &density, + const mfem::Vector &displacement, + const mfem::Vector &gravityGradient, + const mfem::Vector &gravityPotential, + const mean_field::operators::context::gravity_field::GravityFieldRevisions &revisions + ) { + context.Prepare( + {.density = density, + .displacement = displacement, + .gravity_gradient = gravityGradient, + .gravity_potential = gravityPotential}, + revisions + ); + } + + [[nodiscard]] double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const MPI_Comm communicator + ) { + MFEM_VERIFY( + left.Size() == right.Size(), "Cannot compare gravity-displacement-force vectors with " + "different sizes." + ); + + mfem::Vector difference(left); + difference -= right; + + const double scale = std::max( + {gravity_prepared_test_utils::global_norm(left, communicator), + gravity_prepared_test_utils::global_norm(right, communicator), + 100.0 * std::numeric_limits::epsilon()} + ); + + return gravity_prepared_test_utils::global_norm(difference, communicator) / scale; + } + + [[nodiscard]] mfem::Vector centered_difference( + const mean_field::fem::FEM &f, + const mfem::Vector &baseDensity, + const mfem::Vector &densityDirection, + const mfem::Vector &baseGravityGradient, + const mfem::Vector &gravityGradientDirection, + const mfem::Vector &baseDisplacement, + const mfem::Vector &displacementDirection, + const double step + ) { + mfem::Vector plusDensity(baseDensity); + plusDensity.Add(step, densityDirection); + + mfem::Vector minusDensity(baseDensity); + minusDensity.Add(-step, densityDirection); + + mfem::Vector plusGravity(baseGravityGradient); + plusGravity.Add(step, gravityGradientDirection); + + mfem::Vector minusGravity(baseGravityGradient); + minusGravity.Add(-step, gravityGradientDirection); + + mfem::Vector plusDisplacement(baseDisplacement); + plusDisplacement.Add(step, displacementDirection); + + mfem::Vector minusDisplacement(baseDisplacement); + minusDisplacement.Add(-step, displacementDirection); + + mfem::Vector plusResidual; + mfem::Vector minusResidual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, plusDensity, plusGravity, plusDisplacement, plusResidual + ); + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, minusDensity, minusGravity, minusDisplacement, minusResidual + ); + + plusResidual -= minusResidual; + plusResidual /= 2.0 * step; + return plusResidual; + } + + template + [[nodiscard]] mfem::Vector copy_residual_block( + const mfem::Vector &action, + const mean_field::operators::GravityDisplacementForceLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + mfem::Vector result(layout.size(block)); + const int offset = layout.offset(block); + + for (int entry = 0; entry < result.Size(); ++entry) { + result(entry) = action(offset + entry); + } + + return result; + } +} // namespace gravity_displacement_force_test_utils + +TEST_CASE( + "Gravity Displacement Force Query Includes Every Registered Operand", + tags::gravity &tags::quadrature &tags::unit +) { + using DisplacementField = mean_field::field::Field; + + constexpr int geometryWeightOrder = 4; + + constexpr mean_field::quadrature::Query query = + DisplacementField::make_query( + mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, {}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); + + /* + * rho: 2 + * RT value: family order 2 + 1 = 3 + * geometry displacement gradient: 3 - 1 = 2 + * displacement test value: 3 + * reference-element geometry weight: 4 + */ + constexpr int expectedBaseOrder = 2 + 3 + 2 + 3 + 4; + + STATIC_REQUIRE(query.term == mean_field::quadrature::Term::gravity_force); + + STATIC_REQUIRE(query.role == mean_field::quadrature::QuadratureRole::discretization); + + STATIC_REQUIRE(query.domain == mean_field::utils::DOMAINS::STELLAR); + + STATIC_REQUIRE(query.mapping == mean_field::quadrature::MappingKind::general); + + STATIC_REQUIRE(query.base_order.has_value()); + STATIC_REQUIRE(*query.base_order == expectedBaseOrder); +} + +TEST_CASE( + "Gravity Displacement Force Uses Positive Grad-Phi Sign And Excludes " + "Vacuum", + tags::gravity &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()); + + mfem::ParGridFunction densityField(f.densityFes.get()); + mfem::ConstantCoefficient densityCoefficient(1.0); + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector density; + densityField.GetTrueDofs(density); + + mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); + + auto constantGravityFunction = [](const mfem::Vector &, mfem::Vector &value) { + value.SetSize(3); + value = 0.0; + value(0) = 1.0; + }; + + mfem::VectorFunctionCoefficient gravityCoefficient(3, constantGravityFunction); + + gravityField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector gravityGradient; + gravityField.GetTrueDofs(gravityGradient); + + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + mfem::Vector residual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, density, gravityGradient, displacement, residual + ); + + mfem::ParGridFunction testField(f.displacementFes.get()); + testField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector testDirection; + testField.GetTrueDofs(testDirection); + + const double signedWork = gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm()); + + INFO("Constant +x gravity-force work = " << signedWork); + CHECK(signedWork > 0.0); + + const mfem::Vector vacuumDensity = gravity_displacement_force_test_utils::make_vacuum_only_density(f); + + mfem::Vector vacuumResidual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, vacuumDensity, gravityGradient, displacement, vacuumResidual + ); + + CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0); +} + +TEST_CASE( + "Prepared Gravity Displacement Force Reuses Shared Gravity Revisions", + tags::gravity &tags::prepared &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()); + + mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.31); + + const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.47); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + auto revisions = gravity_displacement_force_test_utils::make_revisions(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + gravity_displacement_force_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, revisions + ); + + mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator( + f, *f.domainMapperStateless, gravityContext + ); + + const auto initialReport = preparedOperator.Prepare(); + REQUIRE(initialReport.DidAnyWork()); + REQUIRE(preparedOperator.IsPrepared()); + + mfem::Vector preparedResidual; + mfem::Vector kernelResidual; + + preparedOperator.BuildResidual(preparedResidual); + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, density, gravityGradient, displacement, kernelResidual + ); + + CHECK( + gravity_displacement_force_test_utils::relative_difference( + preparedResidual, kernelResidual, f.mesh->GetComm() + ) < 2.0e-12 + ); + + CHECK_FALSE(preparedOperator.Prepare().DidAnyWork()); + + ++revisions.gravity_potential.value; + + gravity_displacement_force_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, revisions + ); + + CHECK(preparedOperator.IsPrepared()); + CHECK_FALSE(preparedOperator.Prepare().DidAnyWork()); + + density = gravity_displacement_force_test_utils::make_density(f, 0.79); + ++revisions.density.value; + + gravity_displacement_force_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, revisions + ); + + CHECK_FALSE(preparedOperator.IsPrepared()); + + const auto densityReport = preparedOperator.Prepare(); + CHECK(densityReport.DidAnyWork()); + CHECK(preparedOperator.IsPrepared()); + CHECK(preparedOperator.GetResidualPreparationCount() == 2); + CHECK(preparedOperator.GetResidualApplicationCount() == 1); +} + +TEST_CASE( + "Gravity Displacement Force Jacobian Matches All Columns And Centered " + "Differences", + tags::gravity &tags::prepared &tags::jacobian &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()); + + const mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.37); + + const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.53); + + const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.67); + + const mfem::Vector gravityGradientDirection = + gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.71); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.59); + + const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + gravity_displacement_force_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, + gravity_displacement_force_test_utils::make_revisions() + ); + + mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator( + f, *f.domainMapperStateless, gravityContext + ); + + preparedOperator.Prepare(); + + mfem::Vector densityAction; + mfem::Vector gravityAction; + mfem::Vector displacementAction; + mfem::Vector completeAction; + + preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction); + + preparedOperator.ApplyGravityGradientJacobianAction(gravityGradientDirection, gravityAction); + + preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction); + + preparedOperator.ApplyCompleteJacobianAction( + densityDirection, displacementDirection, gravityGradientDirection, completeAction + ); + + mfem::Vector summedColumns(densityAction); + summedColumns += gravityAction; + summedColumns += displacementAction; + + CHECK( + gravity_displacement_force_test_utils::relative_difference(completeAction, summedColumns, f.mesh->GetComm()) < + 2.0e-12 + ); + + mfem::Vector zeroDensity(densityDirection.Size()); + mfem::Vector zeroGravity(gravityGradientDirection.Size()); + mfem::Vector zeroDisplacement(displacementDirection.Size()); + zeroDensity = 0.0; + zeroGravity = 0.0; + zeroDisplacement = 0.0; + + constexpr double step = 1.0e-5; + + const mfem::Vector densityDifference = gravity_displacement_force_test_utils::centered_difference( + f, density, densityDirection, gravityGradient, zeroGravity, displacement, zeroDisplacement, step + ); + + const mfem::Vector gravityDifference = gravity_displacement_force_test_utils::centered_difference( + f, density, zeroDensity, gravityGradient, gravityGradientDirection, displacement, zeroDisplacement, step + ); + + const mfem::Vector displacementDifference = gravity_displacement_force_test_utils::centered_difference( + f, density, zeroDensity, gravityGradient, zeroGravity, displacement, displacementDirection, step + ); + + const mfem::Vector completeDifference = gravity_displacement_force_test_utils::centered_difference( + f, density, densityDirection, gravityGradient, gravityGradientDirection, displacement, displacementDirection, + step + ); + + const double densityError = + gravity_displacement_force_test_utils::relative_difference(densityAction, densityDifference, f.mesh->GetComm()); + + const double gravityError = + gravity_displacement_force_test_utils::relative_difference(gravityAction, gravityDifference, f.mesh->GetComm()); + + const double displacementError = gravity_displacement_force_test_utils::relative_difference( + displacementAction, displacementDifference, f.mesh->GetComm() + ); + + const double completeError = gravity_displacement_force_test_utils::relative_difference( + completeAction, completeDifference, f.mesh->GetComm() + ); + + INFO("Density-column centered-difference error = " << densityError); + INFO("Gravity-column centered-difference error = " << gravityError); + INFO("Displacement-column centered-difference error = " << displacementError); + INFO("Complete centered-difference error = " << completeError); + + CHECK(densityError < 2.0e-9); + CHECK(gravityError < 2.0e-9); + CHECK(displacementError < 2.0e-8); + CHECK(completeError < 3.0e-8); +} + +TEST_CASE( + "Prepared Gravity Displacement Force MFEM Adapter Routes Only R-d", + tags::gravity &tags::prepared &tags::mfem_operators &tags::unit +) { + 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 mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.41); + + const mfem::Vector densityDirection = gravity_displacement_force_test_utils::make_density_direction(f, 0.57); + + const mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.63); + + const mfem::Vector gravityGradientDirection = + gravity_displacement_force_test_utils::make_gravity_gradient_direction(f, 0.77); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.51); + + const mfem::Vector displacementDirection = gravity_displacement_force_test_utils::make_displacement_direction(f); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + gravity_displacement_force_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, + gravity_displacement_force_test_utils::make_revisions() + ); + + mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator( + f, *f.domainMapperStateless, gravityContext + ); + + preparedOperator.Prepare(); + + const auto layout = gravity_displacement_force_test_utils::make_layout(f); + + mean_field::operators::PreparedGravityDisplacementForceJacobianOperator adapter(layout, preparedOperator); + + mfem::BlockVector direction(layout.value_offsets()); + direction = 0.0; + + direction.GetBlock(gravity_displacement_force_test_utils::densityValue) = densityDirection; + + direction.GetBlock(gravity_displacement_force_test_utils::displacementValue) = displacementDirection; + + direction.GetBlock(gravity_displacement_force_test_utils::gravityGradientValue) = gravityGradientDirection; + + direction.GetBlock(gravity_displacement_force_test_utils::gravityPotentialValue) = 0.29; + + direction.GetBlock(gravity_displacement_force_test_utils::enthalpyValue) = -0.37; + + direction.GetBlock(gravity_displacement_force_test_utils::barotropicConstantValue) = 0.43; + + mfem::Vector action; + adapter.Mult(direction, action); + + mfem::Vector expectedDisplacementAction; + + preparedOperator.ApplyCompleteJacobianAction( + densityDirection, displacementDirection, gravityGradientDirection, expectedDisplacementAction + ); + + const mfem::Vector actualDisplacementAction = gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::displacementResidual + ); + + CHECK( + gravity_displacement_force_test_utils::relative_difference( + actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm() + ) < 2.0e-12 + ); + + const std::array zeroRows{ + gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::gravityGradientResidual + ), + gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::gravityPotentialResidual + ), + gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::densityResidual + ), + gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::enthalpyResidual + ), + gravity_displacement_force_test_utils::copy_residual_block( + action, layout, gravity_displacement_force_test_utils::massResidual + ) + }; + + for (const mfem::Vector &row : zeroRows) { + CHECK(gravity_prepared_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0); + } +} \ No newline at end of file diff --git a/tests/operators/gravity_displacement_force_analytic_comparisons.cpp b/tests/operators/gravity_displacement_force_analytic_comparisons.cpp new file mode 100644 index 0000000..1727e36 --- /dev/null +++ b/tests/operators/gravity_displacement_force_analytic_comparisons.cpp @@ -0,0 +1,381 @@ +#include +#include +#include +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace gravity_displacement_force_analytic_test_utils { + struct AffineCase { + const char *name; + std::array scales; + }; + + [[nodiscard]] double analytic_sphere_volume(const double radius) { + return (4.0 / 3.0) * std::numbers::pi * radius * radius * radius; + } + + [[nodiscard]] double determinant( + const std::array< + double, + 3> &scales + ) { + return scales[0] * scales[1] * scales[2]; + } + + [[nodiscard]] double relative_scalar_error( + const double computed, + const double expected + ) { + return std::abs(computed - expected) / std::max(std::abs(expected), 1.0e-30); + } + + [[nodiscard]] mfem::Vector make_constant_density( + const mean_field::fem::FEM &f, + const double densityValue + ) { + mfem::ParGridFunction densityField(f.densityFes.get()); + mfem::ConstantCoefficient densityCoefficient(densityValue); + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector make_reference_gravity( + const mean_field::fem::FEM &f, + const std::array< + double, + 3> &referenceGravity + ) { + mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); + + mfem::VectorFunctionCoefficient gravityCoefficient( + f.mesh->Dimension(), [referenceGravity](const mfem::Vector &, mfem::Vector &value) { + value.SetSize(3); + + for (int component = 0; component < 3; ++component) { + value(component) = referenceGravity[static_cast(component)]; + } + } + ); + + gravityField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector gravityTrue; + gravityField.GetTrueDofs(gravityTrue); + return gravityTrue; + } + + [[nodiscard]] mfem::Vector make_radial_gravity( + const mean_field::fem::FEM &f, + const double radialCoefficient + ) { + mfem::ParGridFunction gravityField(f.gravityFluxFes.get()); + + mfem::VectorFunctionCoefficient gravityCoefficient( + f.mesh->Dimension(), [radialCoefficient](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(position.Size()); + + for (int component = 0; component < position.Size(); ++component) { + value(component) = radialCoefficient * position(component); + } + } + ); + + gravityField.ProjectCoefficient(gravityCoefficient); + + mfem::Vector gravityTrue; + gravityField.GetTrueDofs(gravityTrue); + return gravityTrue; + } + + [[nodiscard]] mfem::Vector make_affine_displacement( + const mean_field::fem::FEM &f, + const std::array< + double, + 3> &scales + ) { + mfem::ParGridFunction displacementField(f.displacementFes.get()); + + mfem::VectorFunctionCoefficient displacementCoefficient( + f.mesh->Dimension(), [scales](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(position.Size()); + + for (int component = 0; component < position.Size(); ++component) { + value(component) = (scales[static_cast(component)] - 1.0) * position(component); + } + } + ); + + displacementField.ProjectCoefficient(displacementCoefficient); + + mfem::Vector displacementTrue; + displacementField.GetTrueDofs(displacementTrue); + return displacementTrue; + } + + [[nodiscard]] mfem::Vector make_constant_test_direction( + const mean_field::fem::FEM &f, + const int selectedComponent + ) { + mfem::ParGridFunction testField(f.displacementFes.get()); + + mfem::VectorFunctionCoefficient testCoefficient( + f.mesh->Dimension(), [selectedComponent](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(position.Size()); + value = 0.0; + value(selectedComponent) = 1.0; + } + ); + + testField.ProjectCoefficient(testCoefficient); + + mfem::Vector testTrue; + testField.GetTrueDofs(testTrue); + return testTrue; + } + + [[nodiscard]] mfem::Vector make_dilation_test_direction(const mean_field::fem::FEM &f) { + mfem::ParGridFunction testField(f.displacementFes.get()); + + mfem::VectorFunctionCoefficient testCoefficient( + f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { value = position; } + ); + + testField.ProjectCoefficient(testCoefficient); + + mfem::Vector testTrue; + testField.GetTrueDofs(testTrue); + return testTrue; + } + + void set_mass_normalized_density( + mean_field::fem::FEM &f, + const double targetMass, + mfem::ParGridFunction &densityField + ) { + const mfem::Vector stellarDensityTrue = gravity_prepared_test_utils::make_domain_supported_density(f, true); + + densityField.SetFromTrueDofs(stellarDensityTrue); + + const double unnormalizedMass = + mean_field::analysis::domain_integrate_grid_function(f, densityField, mean_field::utils::DOMAINS::STELLAR); + + MFEM_VERIFY(unnormalizedMass > 0.0, "The analytic gravity-force test obtained non-positive mass."); + + densityField *= targetMass / unnormalizedMass; + } +} // namespace gravity_displacement_force_analytic_test_utils + +TEST_CASE( + "Gravity Displacement Force Matches Analytic Affine Resultants", + tags::gravity &tags::accuracy &tags::analytic_comparison &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()); + REQUIRE(f.domainMapperStateless != nullptr); + REQUIRE(f.mapping != nullptr); + + constexpr double densityValue = 1.37; + + constexpr std::array physicalGravity{0.31, -0.47, 0.22}; + + constexpr std::array affineCases{ + {{.name = "identity geometry", .scales = {1.0, 1.0, 1.0}}, + {.name = "volume-preserving affine geometry", .scales = {1.14, 0.93, 1.0 / (1.14 * 0.93)}}, + {.name = "volume-changing affine geometry", .scales = {1.11, 0.96, 1.07}}} + }; + + const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue); + + const double referenceVolume = + gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(mean_field::utils::RADIUS); + + constexpr double relativeTolerance = 5.0e-6; + + for (const gravity_displacement_force_analytic_test_utils::AffineCase &affineCase : affineCases) { + DYNAMIC_SECTION(affineCase.name) { + const double mapDeterminant = + gravity_displacement_force_analytic_test_utils::determinant(affineCase.scales); + + REQUIRE(mapDeterminant > 0.0); + + std::array referenceGravity{}; + + /* + * For x = A X, the H(div) Piola relation is + * + * g_phys = A g_ref / det(A). + * + * Prescribe the RT pullback that represents the requested + * constant physical gravity field exactly. + */ + for (int component = 0; component < 3; ++component) { + referenceGravity[static_cast(component)] = + mapDeterminant * physicalGravity[static_cast(component)] / + affineCase.scales[static_cast(component)]; + } + + const mfem::Vector gravityGradient = + gravity_displacement_force_analytic_test_utils::make_reference_gravity(f, referenceGravity); + + const mfem::Vector displacement = + gravity_displacement_force_analytic_test_utils::make_affine_displacement(f, affineCase.scales); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, density, gravityGradient, displacement, residual + ); + + for (int component = 0; component < 3; ++component) { + const mfem::Vector testDirection = + gravity_displacement_force_analytic_test_utils::make_constant_test_direction(f, component); + + const double computedResultant = + gravity_prepared_test_utils::global_dot(residual, testDirection, f.mesh->GetComm()); + + const double expectedResultant = densityValue * physicalGravity[static_cast(component)] * + mapDeterminant * referenceVolume; + + const double relativeError = gravity_displacement_force_analytic_test_utils::relative_scalar_error( + computedResultant, expectedResultant + ); + + CAPTURE(component); + INFO("Map determinant = " << mapDeterminant); + INFO("Computed resultant = " << computedResultant); + INFO("Analytic resultant = " << expectedResultant); + INFO("Relative resultant error = " << relativeError); + + CHECK(relativeError < relativeTolerance); + } + } + } +} + +TEST_CASE( + "Gravity Displacement Force Reproduces Analytic Homogeneous Sphere Work", + tags::gravity &tags::accuracy &tags::analytic_comparison &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()); + REQUIRE(f.domainMapperStateless != nullptr); + + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; + const double volume = gravity_displacement_force_analytic_test_utils::analytic_sphere_volume(radius); + + const double densityValue = mass / volume; + const double radialGravityCoefficient = mean_field::utils::G * mass / (radius * radius * radius); + + const mfem::Vector density = gravity_displacement_force_analytic_test_utils::make_constant_density(f, densityValue); + + const mfem::Vector gravityGradient = + gravity_displacement_force_analytic_test_utils::make_radial_gravity(f, radialGravityCoefficient); + + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + mfem::Vector residual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, density, gravityGradient, displacement, residual + ); + + const mfem::Vector dilationDirection = + gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f); + + const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm()); + + const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius; + + const double relativeError = + gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork); + + INFO("Computed positive gravity work = " << computedWork); + INFO("Analytic positive gravity work = " << analyticWork); + INFO("Computed gravitational virial = " << -computedWork); + INFO("Analytic binding energy = " << -analyticWork); + INFO("Relative analytic work error = " << relativeError); + + REQUIRE(computedWork > 0.0); + CHECK(relativeError < 1.0e-5); +} + +TEST_CASE( + "Solved Homogeneous Sphere Gravity Force Matches Analytic Virial", + tags::gravity &tags::accuracy &tags::analytic_comparison &tags::integration &tags::initialization +) { + mean_field::utils::Args args = test_utils::setup_args(); + args.p.rtol = 1.0e-13; + args.p.max_iters = std::max(args.p.max_iters, 1000); + + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); + + REQUIRE(f.okay()); + REQUIRE(f.domainMapperStateless != nullptr); + + mfem::ParGridFunction displacementField(f.displacementFes.get()); + displacementField = 0.0; + + REQUIRE(f.mapping != nullptr); + f.mapping->ResetDisplacement(); + mean_field::physics::update_stiffness_matrix(f); + + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; + + mfem::ParGridFunction densityField(f.densityFes.get()); + + gravity_displacement_force_analytic_test_utils::set_mass_normalized_density(f, mass, densityField); + + const mean_field::physics::GravitySolution gravitySolution = + mean_field::physics::grav_potential_new(f, args, densityField, displacementField); + + mfem::Vector densityTrue; + mfem::Vector gravityGradientTrue; + mfem::Vector displacementTrue; + + densityField.GetTrueDofs(densityTrue); + gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue); + displacementField.GetTrueDofs(displacementTrue); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_gravity_displacement_force_residual( + f, *f.domainMapperStateless, densityTrue, gravityGradientTrue, displacementTrue, residual + ); + + const mfem::Vector dilationDirection = + gravity_displacement_force_analytic_test_utils::make_dilation_test_direction(f); + + const double computedWork = gravity_prepared_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm()); + + const double analyticWork = (3.0 / 5.0) * mean_field::utils::G * mass * mass / radius; + + const double relativeError = + gravity_displacement_force_analytic_test_utils::relative_scalar_error(computedWork, analyticWork); + + INFO("Solved-field positive gravity work = " << computedWork); + INFO("Analytic positive gravity work = " << analyticWork); + INFO("Solved-field gravitational virial = " << -computedWork); + INFO("Analytic homogeneous-sphere binding energy = " << -analyticWork); + INFO("Relative solved-field virial error = " << relativeError); + + REQUIRE(computedWork > 0.0); + CHECK(relativeError < 1.0e-5); +} \ No newline at end of file diff --git a/tests/operators/gravity_field.cpp b/tests/operators/gravity_field.cpp index c3049e8..cb7b087 100644 --- a/tests/operators/gravity_field.cpp +++ b/tests/operators/gravity_field.cpp @@ -12,17 +12,13 @@ using namespace mean_field; using Catch::Matchers::WithinAbs; namespace { - namespace blocks = utils::blocks; - using form = blocks::gravity_field_form; + namespace blocks = utils::blocks; + using form = blocks::gravity_field_form; - constexpr auto density_block = - blocks::get_value_block(blocks::density_field.mass_term); - constexpr auto displacement_block = - blocks::get_value_block(blocks::displacement_field.geometry_term); - constexpr auto gravity_gradient_block = - blocks::get_value_block(blocks::gravity_field.gradient_term); - constexpr auto gravity_potential_block = - blocks::get_value_block(blocks::gravity_field.poisson_term); + constexpr auto density_block = blocks::get_value_block(blocks::density_field.mass_term); + constexpr auto displacement_block = blocks::get_value_block(blocks::displacement_field.geometry_term); + constexpr auto gravity_gradient_block = blocks::get_value_block(blocks::gravity_field.gradient_term); + constexpr auto gravity_potential_block = blocks::get_value_block(blocks::gravity_field.poisson_term); constexpr auto gravity_gradient_residual_block = blocks::get_residual_block(blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = @@ -30,14 +26,12 @@ namespace { blocks::form_layout make_gravity_layout(const fem::FEM &f) { const std::array value_sizes{ - f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), - f.gravityFluxFes->GetTrueVSize(), + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; const std::array residual_sizes{ - f.gravityFluxFes->GetTrueVSize(), - f.gravityPotentialFes->GetTrueVSize() + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; return blocks::form_layout(value_sizes, residual_sizes); @@ -81,15 +75,13 @@ namespace { ) { mfem::Vector vector(size); for (int i = 0; i < size; ++i) - vector(i) = - 0.4 * std::sin(0.37 * static_cast(i + 1) + phase) + - 0.2 * std::cos(0.19 * static_cast(i + 1) - phase); + vector(i) = 0.4 * std::sin(0.37 * static_cast(i + 1) + phase) + + 0.2 * std::cos(0.19 * static_cast(i + 1) - phase); return vector; } mfem::Vector make_displacement(const fem::FEM &f) { - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = 0.015 * position(0) + 0.004 * position(1); value(1) = -0.003 * position(0) + 0.012 * position(1); @@ -129,8 +121,7 @@ namespace { mfem::Vector element_values; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - if (f.mesh->GetAttribute(element_id) != - f.domainMapperStateless->GetVacuumElementAttribute()) + if (f.mesh->GetAttribute(element_id) != f.domainMapperStateless->GetVacuumElementAttribute()) continue; f.densityFes->GetElementDofs(element_id, element_dofs); @@ -153,17 +144,14 @@ namespace { mfem::Vector difference(lhs); difference -= rhs; - return difference.Norml2() / - std::max({lhs.Norml2(), rhs.Norml2(), 1.0e-14}); + return difference.Norml2() / std::max({lhs.Norml2(), rhs.Norml2(), 1.0e-14}); } mfem::Vector make_core_supported_gravity_gradient(const fem::FEM &f) { - constexpr double support_radius = 0.15 * utils::RADIUS; - constexpr double support_radius_squared = - support_radius * support_radius; + constexpr double support_radius = 0.15 * utils::RADIUS; + constexpr double support_radius_squared = support_radius * support_radius; - auto field_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto field_function = [](const mfem::Vector &position, mfem::Vector &value) { const double radius_squared = position * position; value.SetSize(3); @@ -172,14 +160,12 @@ namespace { if (radius_squared >= support_radius_squared) return; - const double normalized_radius_squared = - radius_squared / support_radius_squared; - const double envelope = - std::pow(1.0 - normalized_radius_squared, 3.0); + const double normalized_radius_squared = radius_squared / support_radius_squared; + const double envelope = std::pow(1.0 - normalized_radius_squared, 3.0); - value(0) = envelope; - value(1) = -0.4 * envelope; - value(2) = 0.7 * envelope; + value(0) = envelope; + value(1) = -0.4 * envelope; + value(2) = 0.7 * envelope; }; mfem::VectorFunctionCoefficient coefficient(3, field_function); @@ -199,10 +185,7 @@ namespace { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -231,13 +214,9 @@ namespace { long long vacuum_elements{0}; long long stellar_quadrature_points{0}; long long vacuum_quadrature_points{0}; - double minimum_stellar_determinant{ - std::numeric_limits::infinity() - }; + double minimum_stellar_determinant{std::numeric_limits::infinity()}; double maximum_stellar_determinant{0.0}; - double minimum_vacuum_determinant{ - std::numeric_limits::infinity() - }; + double minimum_vacuum_determinant{std::numeric_limits::infinity()}; double maximum_vacuum_determinant{0.0}; }; @@ -278,10 +257,7 @@ namespace { ) { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -292,9 +268,7 @@ namespace { ) { const double local_dot = lhs * rhs; double global_dot = 0.0; - MPI_Allreduce( - &local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator); return global_dot; } @@ -306,10 +280,7 @@ namespace { mfem::Vector difference(computed); difference -= reference; return global_vector_norm(difference, communicator) / - std::max( - global_vector_norm(reference, communicator), - std::numeric_limits::epsilon() - ); + std::max(global_vector_norm(reference, communicator), std::numeric_limits::epsilon()); } const mfem::IntegrationRule &get_stateless_hdiv_reference_rule( @@ -317,34 +288,28 @@ namespace { const mfem::FiniteElement &element, const mfem::ElementTransformation &transformation ) { - const bool is_vacuum = - transformation.Attribute == - f.domainMapperStateless->GetVacuumElementAttribute(); + const bool is_vacuum = transformation.Attribute == f.domainMapperStateless->GetVacuumElementAttribute(); const quadrature::Query query{ - .term = quadrature::Term::gravity_hdiv_mass, - .role = quadrature::QuadratureRole::discretization, - .domain = utils::DOMAINS::ALL, - .mapping = is_vacuum ? quadrature::MappingKind::kelvin - : quadrature::MappingKind::general, - .trial_order = element.GetOrder(), - .test_order = element.GetOrder(), - .coefficient_order = 0, + .term = quadrature::Term::gravity_hdiv_mass, + .role = quadrature::QuadratureRole::discretization, + .domain = utils::DOMAINS::ALL, + .mapping = is_vacuum ? quadrature::MappingKind::kelvin : quadrature::MappingKind::general, + .trial_order = element.GetOrder(), + .test_order = element.GetOrder(), + .coefficient_order = 0, .geometry_weight_order = transformation.OrderW() }; - return *f.quadratureFactory - ->get(query, transformation.GetGeometryType()) - .integration_rule; + return *f.quadratureFactory->get(query, transformation.GetGeometryType()).integration_rule; } mfem::Vector make_full_support_gravity_gradient(const fem::FEM &f) { mfem::Vector gravity_gradient(f.gravityFluxFes->GetTrueVSize()); for (int i = 0; i < gravity_gradient.Size(); ++i) { - const double index = static_cast(i + 1); - gravity_gradient(i) = - std::sin(0.37 * index) + 0.31 * std::cos(0.19 * index); + const double index = static_cast(i + 1); + gravity_gradient(i) = std::sin(0.37 * index) + 0.31 * std::cos(0.19 * index); } return gravity_gradient; @@ -358,20 +323,14 @@ namespace { displacement = 0.0; if (deformed) { - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); - value(0) = - 0.04 * position(0) + 0.01 * position(1) * position(2); - value(1) = - -0.03 * position(1) + 0.008 * position(0) * position(2); - value(2) = - 0.02 * position(2) - 0.006 * position(0) * position(1); + value(0) = 0.04 * position(0) + 0.01 * position(1) * position(2); + value(1) = -0.03 * position(1) + 0.008 * position(0) * position(2); + value(2) = 0.02 * position(2) - 0.006 * position(0) * position(1); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, displacement_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function); displacement.ProjectCoefficient(displacement_coefficient); } @@ -403,26 +362,16 @@ namespace { return "unknown"; } - StatelessHDivMassReference - evaluate_stateless_hdiv_mass_quadrature_reference( + StatelessHDivMassReference evaluate_stateless_hdiv_mass_quadrature_reference( const fem::FEM &fem, const mfem::Vector &gravity_gradient_true, const mfem::Vector &displacement_true ) { - MFEM_VERIFY( - fem.domainMapperStateless != nullptr, - "The stateless domain mapper is unavailable." - ); + MFEM_VERIFY(fem.domainMapperStateless != nullptr, "The stateless domain mapper is unavailable."); - MFEM_VERIFY( - fem.compactificationFes != nullptr, - "The compactification finite-element space is unavailable." - ); + MFEM_VERIFY(fem.compactificationFes != nullptr, "The compactification finite-element space is unavailable."); - MFEM_VERIFY( - fem.compactificationCoordinate != nullptr, - "The compactification coordinate is unavailable." - ); + MFEM_VERIFY(fem.compactificationCoordinate != nullptr, "The compactification coordinate is unavailable."); MFEM_VERIFY( gravity_gradient_true.Size() == fem.gravityFluxFes->GetTrueVSize(), @@ -437,13 +386,9 @@ namespace { mfem::Vector gravity_gradient_local; mfem::Vector displacement_local; - reference_true_to_local( - *fem.gravityFluxFes, gravity_gradient_true, gravity_gradient_local - ); + reference_true_to_local(*fem.gravityFluxFes, gravity_gradient_true, gravity_gradient_local); - reference_true_to_local( - *fem.displacementFes, displacement_true, displacement_local - ); + reference_true_to_local(*fem.displacementFes, displacement_true, displacement_local); mfem::Vector total_local(fem.gravityFluxFes->GetVSize()); @@ -457,9 +402,7 @@ namespace { StatelessHDivMassReference reference; - mapping::DomainMapperStateless::Workspace workspace( - fem.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(fem.mesh->Dimension()); mapping::VolumeMappingContext mapping_context; @@ -477,79 +420,55 @@ namespace { mfem::DenseMatrix vector_shape; mfem::DenseMatrix mapped_mass_tensor; - const int vacuum_attribute = - fem.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = fem.domainMapperStateless->GetVacuumElementAttribute(); for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) { - const mfem::FiniteElement &gravity_element = - *fem.gravityFluxFes->GetFE(element_id); + const mfem::FiniteElement &gravity_element = *fem.gravityFluxFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *fem.compactificationFes->GetFE(element_id); - mfem::ElementTransformation &transformation = - *fem.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation &transformation = *fem.mesh->GetElementTransformation(element_id); - const bool is_vacuum = transformation.Attribute == vacuum_attribute; + const bool is_vacuum = transformation.Attribute == vacuum_attribute; mfem::DofTransformation *gravity_dof_transformation = fem.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs); mfem::DofTransformation *displacement_dof_transformation = - fem.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + fem.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - fem.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + fem.compactificationFes->GetElementDofs(element_id, compactification_dofs); - gravity_gradient_local.GetSubVector( - gravity_dofs, element_gravity_gradient - ); + gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient); - displacement_local.GetSubVector( - displacement_dofs, element_displacement - ); + displacement_local.GetSubVector(displacement_dofs, element_displacement); - fem.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + fem.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (gravity_dof_transformation != nullptr) { - gravity_dof_transformation->InvTransformPrimal( - element_gravity_gradient - ); + gravity_dof_transformation->InvTransformPrimal(element_gravity_gradient); } if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); } if (compactification_dof_transformation != nullptr) { - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); } const mapping::ElementDisplacementData displacement_data = - mapping::ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); + mapping::ElementDisplacementDataFromElementVDofs(displacement_element, element_displacement); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; const int gravity_dof_count = gravity_element.GetDof(); @@ -567,98 +486,71 @@ namespace { mapped_mass_tensor.SetSize(dimension, dimension); const mfem::IntegrationRule &integration_rule = - get_stateless_hdiv_reference_rule( - fem, gravity_element, transformation - ); + get_stateless_hdiv_reference_rule(fem, gravity_element, transformation); - for (int quadrature_point = 0; - quadrature_point < integration_rule.GetNPoints(); - ++quadrature_point) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(quadrature_point); + for (int quadrature_point = 0; quadrature_point < integration_rule.GetNPoints(); ++quadrature_point) { + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(quadrature_point); transformation.SetIntPoint(&integration_point); - const mapping::MappingStatus status = - fem.domainMapperStateless->EvaluateVolume( - mapping_data, transformation, integration_point, - workspace, mapping_context - ); + const mapping::MappingStatus status = fem.domainMapperStateless->EvaluateVolume( + mapping_data, transformation, integration_point, workspace, mapping_context + ); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Stateless mapping failed while evaluating the H(div) " "quadrature reference." - << "\nElement ID = " << element_id - << "\nElement attribute = " << transformation.Attribute + << "\nElement ID = " << element_id << "\nElement attribute = " << transformation.Attribute << "\nQuadrature point = " << quadrature_point << "\nMapping status = " << mapping_status_name(status) ); gravity_element.CalcVShape(transformation, vector_shape); - mean_field::mapping::ComputeHDivMassTensor( - mapping_context.mapping, mapped_mass_tensor - ); + mean_field::mapping::ComputeHDivMassTensor(mapping_context.mapping, mapped_mass_tensor); // Evaluate B*x at this quadrature point. - vector_shape.MultTranspose( - element_gravity_gradient, quadrature_flux - ); + vector_shape.MultTranspose(element_gravity_gradient, quadrature_flux); // Apply the mapped H(div) mass tensor. - mapped_mass_tensor.Mult( - quadrature_flux, mapped_quadrature_flux - ); + mapped_mass_tensor.Mult(quadrature_flux, mapped_quadrature_flux); - const double weight = - integration_point.weight * transformation.Weight(); + const double weight = integration_point.weight * transformation.Weight(); // Accumulate B^T*D*B*x directly. for (int dof = 0; dof < gravity_dof_count; ++dof) { double contribution = 0.0; - for (int component = 0; component < dimension; - ++component) { - contribution += vector_shape(dof, component) * - mapped_quadrature_flux(component); + for (int component = 0; component < dimension; ++component) { + contribution += vector_shape(dof, component) * mapped_quadrature_flux(component); } element_action(dof) += weight * contribution; } - const double mapping_determinant = - mapping_context.mapping.mapping_determinant; + const double mapping_determinant = mapping_context.mapping.mapping_determinant; MFEM_VERIFY( - std::isfinite(mapping_determinant) && - mapping_determinant > 0.0, + std::isfinite(mapping_determinant) && mapping_determinant > 0.0, "The quadrature reference encountered an invalid mapping " "determinant." ); if (is_vacuum) { - reference.minimum_vacuum_determinant = std::min( - reference.minimum_vacuum_determinant, - mapping_determinant - ); + reference.minimum_vacuum_determinant = + std::min(reference.minimum_vacuum_determinant, mapping_determinant); - reference.maximum_vacuum_determinant = std::max( - reference.maximum_vacuum_determinant, - mapping_determinant - ); + reference.maximum_vacuum_determinant = + std::max(reference.maximum_vacuum_determinant, mapping_determinant); ++reference.vacuum_quadrature_points; } else { - reference.minimum_stellar_determinant = std::min( - reference.minimum_stellar_determinant, - mapping_determinant - ); + reference.minimum_stellar_determinant = + std::min(reference.minimum_stellar_determinant, mapping_determinant); - reference.maximum_stellar_determinant = std::max( - reference.maximum_stellar_determinant, - mapping_determinant - ); + reference.maximum_stellar_determinant = + std::max(reference.maximum_stellar_determinant, mapping_determinant); ++reference.stellar_quadrature_points; } @@ -681,31 +573,22 @@ namespace { } } - reference_local_to_true( - *fem.gravityFluxFes, total_local, reference.total_action - ); + reference_local_to_true(*fem.gravityFluxFes, total_local, reference.total_action); - reference_local_to_true( - *fem.gravityFluxFes, stellar_local, reference.stellar_action - ); + reference_local_to_true(*fem.gravityFluxFes, stellar_local, reference.stellar_action); - reference_local_to_true( - *fem.gravityFluxFes, vacuum_local, reference.vacuum_action - ); + reference_local_to_true(*fem.gravityFluxFes, vacuum_local, reference.vacuum_action); const MPI_Comm communicator = fem.gravityFluxFes->GetComm(); const long long local_counts[4]{ - reference.stellar_elements, reference.vacuum_elements, - reference.stellar_quadrature_points, + reference.stellar_elements, reference.vacuum_elements, reference.stellar_quadrature_points, reference.vacuum_quadrature_points }; long long global_counts[4]{}; - MPI_Allreduce( - local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator - ); + MPI_Allreduce(local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator); reference.stellar_elements = global_counts[0]; @@ -715,28 +598,16 @@ namespace { reference.vacuum_quadrature_points = global_counts[3]; - const double local_minimums[2]{ - reference.minimum_stellar_determinant, - reference.minimum_vacuum_determinant - }; + const double local_minimums[2]{reference.minimum_stellar_determinant, reference.minimum_vacuum_determinant}; - const double local_maximums[2]{ - reference.maximum_stellar_determinant, - reference.maximum_vacuum_determinant - }; + const double local_maximums[2]{reference.maximum_stellar_determinant, reference.maximum_vacuum_determinant}; double global_minimums[2]{}; double global_maximums[2]{}; - MPI_Allreduce( - local_minimums, global_minimums, 2, MPI_DOUBLE, MPI_MIN, - communicator - ); + MPI_Allreduce(local_minimums, global_minimums, 2, MPI_DOUBLE, MPI_MIN, communicator); - MPI_Allreduce( - local_maximums, global_maximums, 2, MPI_DOUBLE, MPI_MAX, - communicator - ); + MPI_Allreduce(local_maximums, global_maximums, 2, MPI_DOUBLE, MPI_MAX, communicator); reference.minimum_stellar_determinant = global_minimums[0]; @@ -753,16 +624,13 @@ namespace { using gravity_layout = blocks::form_layout; gravity_layout make_gravity_jacobian_layout(const fem::FEM &f) { const std::array value_sizes{ - f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), - f.gravityFluxFes->GetTrueVSize(), + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; - const std::array - residual_sizes{ - f.gravityFluxFes->GetTrueVSize(), - f.gravityPotentialFes->GetTrueVSize() - }; + const std::array residual_sizes{ + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() + }; return gravity_layout(value_sizes, residual_sizes); } @@ -814,8 +682,7 @@ namespace { for (int i = 0; i < layout.size(block); ++i) { const double index_value = static_cast(i + 1); vector(layout.offset(block) + i) = - scale * (std::sin(0.31 * index_value + phase) + - 0.37 * std::cos(0.17 * index_value - phase)); + scale * (std::sin(0.31 * index_value + phase) + 0.37 * std::cos(0.17 * index_value - phase)); } } @@ -831,8 +698,7 @@ namespace { fill_value_block(state, layout, gravity_gradient_block, 0.4, 0.23); fill_value_block(state, layout, gravity_potential_block, 0.3, 0.37); - const mfem::Vector displacement = - make_stateless_reference_displacement(f, deformed); + const mfem::Vector displacement = make_stateless_reference_displacement(f, deformed); set_value_block(state, layout, displacement_block, displacement); return state; @@ -852,23 +718,17 @@ namespace { return direction; } - mfem::Vector - make_gravity_potential_direction(const gravity_layout &layout) { + mfem::Vector make_gravity_potential_direction(const gravity_layout &layout) { mfem::Vector direction(layout.value_offsets().Last()); direction = 0.0; - fill_value_block( - direction, layout, gravity_potential_block, 0.09, 0.41 - ); + fill_value_block(direction, layout, gravity_potential_block, 0.09, 0.41); return direction; } - mfem::Vector - make_combined_fixed_geometry_direction(const gravity_layout &layout) { - mfem::Vector direction = make_density_direction(layout); - const mfem::Vector gravity_gradient_direction = - make_gravity_gradient_direction(layout); - const mfem::Vector gravity_potential_direction = - make_gravity_potential_direction(layout); + mfem::Vector make_combined_fixed_geometry_direction(const gravity_layout &layout) { + mfem::Vector direction = make_density_direction(layout); + const mfem::Vector gravity_gradient_direction = make_gravity_gradient_direction(layout); + const mfem::Vector gravity_potential_direction = make_gravity_potential_direction(layout); direction += gravity_gradient_direction; direction += gravity_potential_direction; return direction; @@ -880,9 +740,7 @@ namespace { ) { mfem::Vector direction(layout.value_offsets().Last()); direction = 0.0; - set_value_block( - direction, layout, displacement_block, displacement_direction - ); + set_value_block(direction, layout, displacement_block, displacement_direction); return direction; } @@ -925,56 +783,42 @@ namespace { mfem::Vector difference(computed); difference -= reference; - return global_norm(difference, communicator) / - std::max(global_norm(reference, communicator), 1.0e-14); + return global_norm(difference, communicator) / std::max(global_norm(reference, communicator), 1.0e-14); } - HdivMassVariationTestFields - make_hdiv_mass_variation_test_fields(const fem::FEM &f) { + HdivMassVariationTestFields make_hdiv_mass_variation_test_fields(const fem::FEM &f) { const int dimension = f.mesh->Dimension(); - auto gravity_gradient_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto gravity_gradient_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); - value(0) = - 0.4 + 0.18 * position(0) - 0.07 * position(1) * position(2); - value(1) = - -0.3 + 0.11 * position(1) + 0.05 * position(0) * position(2); - value(2) = - 0.2 - 0.09 * position(2) + 0.04 * position(0) * position(1); + value(0) = 0.4 + 0.18 * position(0) - 0.07 * position(1) * position(2); + value(1) = -0.3 + 0.11 * position(1) + 0.05 * position(0) * position(2); + value(2) = 0.2 - 0.09 * position(2) + 0.04 * position(0) * position(1); }; - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = 0.025 * position(0) + 0.006 * position(1) * position(2); value(1) = -0.018 * position(1) + 0.005 * position(0) * position(2); value(2) = 0.014 * position(2) + 0.004 * position(0) * position(1); }; - auto displacement_direction_1_function = - [](const mfem::Vector &position, mfem::Vector &value) { - value.SetSize(3); - value(0) = 0.16 * position(0) + 0.03 * position(1); - value(1) = -0.11 * position(1) + 0.02 * position(2); - value(2) = 0.13 * position(2) - 0.025 * position(0); - }; + auto displacement_direction_1_function = [](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = 0.16 * position(0) + 0.03 * position(1); + value(1) = -0.11 * position(1) + 0.02 * position(2); + value(2) = 0.13 * position(2) - 0.025 * position(0); + }; - auto displacement_direction_2_function = - [](const mfem::Vector &position, mfem::Vector &value) { - value.SetSize(3); - value(0) = -0.07 * position(1) + 0.025 * position(2); - value(1) = 0.09 * position(0) + 0.04 * position(2); - value(2) = - -0.08 * position(2) + 0.03 * position(0) * position(1); - }; + auto displacement_direction_2_function = [](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = -0.07 * position(1) + 0.025 * position(2); + value(1) = 0.09 * position(0) + 0.04 * position(2); + value(2) = -0.08 * position(2) + 0.03 * position(0) * position(1); + }; - mfem::VectorFunctionCoefficient gravity_gradient_coefficient( - dimension, gravity_gradient_function - ); - mfem::VectorFunctionCoefficient displacement_coefficient( - dimension, displacement_function - ); + mfem::VectorFunctionCoefficient gravity_gradient_coefficient(dimension, gravity_gradient_function); + mfem::VectorFunctionCoefficient displacement_coefficient(dimension, displacement_function); mfem::VectorFunctionCoefficient displacement_direction_1_coefficient( dimension, displacement_direction_1_function ); @@ -984,31 +828,19 @@ namespace { mfem::ParGridFunction gravity_gradient_grid(f.gravityFluxFes.get()); mfem::ParGridFunction displacement_grid(f.displacementFes.get()); - mfem::ParGridFunction displacement_direction_1_grid( - f.displacementFes.get() - ); - mfem::ParGridFunction displacement_direction_2_grid( - f.displacementFes.get() - ); + mfem::ParGridFunction displacement_direction_1_grid(f.displacementFes.get()); + mfem::ParGridFunction displacement_direction_2_grid(f.displacementFes.get()); gravity_gradient_grid.ProjectCoefficient(gravity_gradient_coefficient); displacement_grid.ProjectCoefficient(displacement_coefficient); - displacement_direction_1_grid.ProjectCoefficient( - displacement_direction_1_coefficient - ); - displacement_direction_2_grid.ProjectCoefficient( - displacement_direction_2_coefficient - ); + displacement_direction_1_grid.ProjectCoefficient(displacement_direction_1_coefficient); + displacement_direction_2_grid.ProjectCoefficient(displacement_direction_2_coefficient); HdivMassVariationTestFields fields; gravity_gradient_grid.GetTrueDofs(fields.gravity_gradient); displacement_grid.GetTrueDofs(fields.displacement); - displacement_direction_1_grid.GetTrueDofs( - fields.displacement_direction_1 - ); - displacement_direction_2_grid.GetTrueDofs( - fields.displacement_direction_2 - ); + displacement_direction_1_grid.GetTrueDofs(fields.displacement_direction_1); + displacement_direction_2_grid.GetTrueDofs(fields.displacement_direction_2); return fields; } @@ -1029,9 +861,7 @@ namespace { mfem::Vector plus_action; mfem::Vector minus_action; - operators::kernels::apply_mapped_hdiv_mass( - f, domain_mapper, gravity_gradient, plus_displacement, plus_action - ); + operators::kernels::apply_mapped_hdiv_mass(f, domain_mapper, gravity_gradient, plus_displacement, plus_action); operators::kernels::apply_mapped_hdiv_mass( f, domain_mapper, gravity_gradient, minus_displacement, minus_action ); @@ -1044,8 +874,8 @@ namespace { mfem::Vector make_source_variation_density(const fem::FEM &f) { auto density_function = [](const mfem::Vector &position) { - return 1.2 + 0.16 * position(0) - 0.09 * position(1) + - 0.07 * position(2) + 0.04 * position(0) * position(1); + return 1.2 + 0.16 * position(0) - 0.09 * position(1) + 0.07 * position(2) + + 0.04 * position(0) * position(1); }; mfem::FunctionCoefficient density_coefficient(density_function); @@ -1074,12 +904,8 @@ namespace { mfem::Vector plus_action; mfem::Vector minus_action; - operators::kernels::apply_mapped_source( - f, domain_mapper, density, plus_displacement, plus_action - ); - operators::kernels::apply_mapped_source( - f, domain_mapper, density, minus_displacement, minus_action - ); + operators::kernels::apply_mapped_source(f, domain_mapper, density, plus_displacement, plus_action); + operators::kernels::apply_mapped_source(f, domain_mapper, density, minus_displacement, minus_action); plus_action -= minus_action; plus_action /= 2.0 * difference_step; @@ -1096,28 +922,20 @@ namespace { const double local_dot = left * right; double global_dot_value = 0.0; - MPI_Allreduce( - &local_dot, &global_dot_value, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_dot, &global_dot_value, 1, MPI_DOUBLE, MPI_SUM, communicator); return global_dot_value; } mfem::Vector make_secondary_gravity_gradient(const fem::FEM &f) { - auto gravity_gradient_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto gravity_gradient_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); - value(0) = - -0.17 + 0.09 * position(1) + 0.03 * position(0) * position(2); - value(1) = - 0.31 - 0.14 * position(0) + 0.05 * position(1) * position(2); - value(2) = - -0.22 + 0.12 * position(2) - 0.04 * position(0) * position(1); + value(0) = -0.17 + 0.09 * position(1) + 0.03 * position(0) * position(2); + value(1) = 0.31 - 0.14 * position(0) + 0.05 * position(1) * position(2); + value(2) = -0.22 + 0.12 * position(2) - 0.04 * position(0) * position(1); }; - mfem::VectorFunctionCoefficient coefficient( - f.mesh->Dimension(), gravity_gradient_function - ); + mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), gravity_gradient_function); mfem::ParGridFunction grid_function(f.gravityFluxFes.get()); mfem::Vector true_dofs; @@ -1129,8 +947,8 @@ namespace { mfem::Vector make_secondary_source_density(const fem::FEM &f) { auto density_function = [](const mfem::Vector &position) { - return 0.8 - 0.11 * position(0) + 0.13 * position(1) - - 0.06 * position(2) + 0.03 * position(1) * position(2); + return 0.8 - 0.11 * position(0) + 0.13 * position(1) - 0.06 * position(2) + + 0.03 * position(1) * position(2); }; mfem::FunctionCoefficient coefficient(density_function); @@ -1154,17 +972,14 @@ namespace { mfem::Vector element_values; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); REQUIRE(transformation != nullptr); if (transformation->Attribute != vacuum_attribute) continue; - const mfem::FiniteElement &element = - *f.densityFes->GetFE(element_id); - mfem::DofTransformation *dof_transformation = - f.densityFes->GetElementDofs(element_id, element_dofs); + const mfem::FiniteElement &element = *f.densityFes->GetFE(element_id); + mfem::DofTransformation *dof_transformation = f.densityFes->GetElementDofs(element_id, element_dofs); element_values.SetSize(element.GetDof()); for (int i = 0; i < element_values.Size(); ++i) @@ -1189,57 +1004,37 @@ namespace { ) { const int offset = offsets[index]; const int size = offsets[index + 1] - offset; - return mfem::Vector( - const_cast(vector.GetData()) + offset, size - ); + return mfem::Vector(const_cast(vector.GetData()) + offset, size); } void check_linearization_context_matches_state( - const operators::context::gravity_field:: - GravityFieldLinearizationContext &context, + const operators::context::gravity_field::GravityFieldLinearizationContext &context, const mfem::Vector &state, const mfem::Array &state_offsets, MPI_Comm communicator ) { using form = blocks::gravity_field_form; - constexpr auto density_block = utils::blocks::get_value_block( - blocks::density_field.mass_term - ); + constexpr auto density_block = utils::blocks::get_value_block(blocks::density_field.mass_term); constexpr auto displacement_block = - utils::blocks::get_value_block( - blocks::displacement_field.geometry_term - ); + utils::blocks::get_value_block(blocks::displacement_field.geometry_term); constexpr auto gravity_gradient_block = - utils::blocks::get_value_block( - blocks::gravity_field.gradient_term - ); + utils::blocks::get_value_block(blocks::gravity_field.gradient_term); - const mfem::Vector density = - make_read_only_value_view(state, state_offsets, density_block); - const mfem::Vector displacement = - make_read_only_value_view(state, state_offsets, displacement_block); - const mfem::Vector gravity_gradient = make_read_only_value_view( - state, state_offsets, gravity_gradient_block - ); + const mfem::Vector density = make_read_only_value_view(state, state_offsets, density_block); + const mfem::Vector displacement = make_read_only_value_view(state, state_offsets, displacement_block); + const mfem::Vector gravity_gradient = make_read_only_value_view(state, state_offsets, gravity_gradient_block); CHECK_THAT( - global_relative_vector_error( - context.GetDensity(), density, communicator - ), + global_relative_vector_error(context.GetDensity(), density, communicator), Catch::Matchers::WithinAbs(0.0, 0.0) ); CHECK_THAT( - global_relative_vector_error( - context.GetGeometryContext().GetDisplacement(), displacement, - communicator - ), + global_relative_vector_error(context.GetGeometryContext().GetDisplacement(), displacement, communicator), Catch::Matchers::WithinAbs(0.0, 0.0) ); CHECK_THAT( - global_relative_vector_error( - context.GetGravityGradient(), gravity_gradient, communicator - ), + global_relative_vector_error(context.GetGravityGradient(), gravity_gradient, communicator), Catch::Matchers::WithinAbs(0.0, 0.0) ); } @@ -1248,26 +1043,13 @@ namespace { const fem::FEM &f, const mfem::Vector &gravity_gradient_true ) { - MFEM_VERIFY( - f.mesh != nullptr, "The legacy H(div) reference requires a mesh." - ); - MFEM_VERIFY( - f.gravityFluxFes != nullptr, - "The legacy H(div) reference requires the RT finite-element space." - ); - MFEM_VERIFY( - f.mapping != nullptr, - "The legacy H(div) reference requires the legacy domain mapper." - ); - MFEM_VERIFY( - f.domainMapperStateless != nullptr, - "The vacuum attribute is unavailable." - ); + MFEM_VERIFY(f.mesh != nullptr, "The legacy H(div) reference requires a mesh."); + MFEM_VERIFY(f.gravityFluxFes != nullptr, "The legacy H(div) reference requires the RT finite-element space."); + MFEM_VERIFY(f.mapping != nullptr, "The legacy H(div) reference requires the legacy domain mapper."); + MFEM_VERIFY(f.domainMapperStateless != nullptr, "The vacuum attribute is unavailable."); mfem::Vector gravity_gradient_local; - reference_true_to_local( - *f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local - ); + reference_true_to_local(*f.gravityFluxFes, gravity_gradient_true, gravity_gradient_local); mfem::Vector total_local(f.gravityFluxFes->GetVSize()); mfem::Vector stellar_local(f.gravityFluxFes->GetVSize()); @@ -1278,31 +1060,24 @@ namespace { vacuum_local = 0.0; StatelessHDivMassReference reference; - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - const mfem::FiniteElement &gravity_element = - *f.gravityFluxFes->GetFE(element_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); MFEM_VERIFY( - transformation != nullptr, - "The legacy H(div) reference received a null element " - "transformation." + transformation != nullptr, "The legacy H(div) reference received a null element " + "transformation." ); - const bool is_vacuum = - transformation->Attribute == vacuum_attribute; + const bool is_vacuum = transformation->Attribute == vacuum_attribute; mfem::Array gravity_dofs; f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs); mfem::Vector element_gravity_gradient; - gravity_gradient_local.GetSubVector( - gravity_dofs, element_gravity_gradient - ); + gravity_gradient_local.GetSubVector(gravity_dofs, element_gravity_gradient); const int gravity_dof_count = gravity_element.GetDof(); const int dimension = transformation->GetSpaceDim(); @@ -1315,13 +1090,10 @@ namespace { element_matrix = 0.0; const mfem::IntegrationRule &integration_rule = - get_stateless_hdiv_reference_rule( - f, gravity_element, *transformation - ); + get_stateless_hdiv_reference_rule(f, gravity_element, *transformation); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); /* @@ -1338,15 +1110,12 @@ namespace { REQUIRE(std::isfinite(mapping_determinant)); REQUIRE(mapping_determinant > 0.0); - mfem::MultAtB( - mapping_jacobian, mapping_jacobian, mapped_mass_tensor - ); + mfem::MultAtB(mapping_jacobian, mapping_jacobian, mapped_mass_tensor); mapped_mass_tensor *= 1.0 / std::abs(mapping_determinant); gravity_element.CalcVShape(*transformation, vector_shape); - const double weight = - integration_point.weight * transformation->Weight(); + const double weight = integration_point.weight * transformation->Weight(); for (int i = 0; i < gravity_dof_count; ++i) { for (int j = 0; j < gravity_dof_count; ++j) { @@ -1354,9 +1123,8 @@ namespace { for (int row = 0; row < dimension; ++row) { for (int column = 0; column < dimension; ++column) { - entry += vector_shape(i, row) * - mapped_mass_tensor(row, column) * - vector_shape(j, column); + entry += + vector_shape(i, row) * mapped_mass_tensor(row, column) * vector_shape(j, column); } } @@ -1385,29 +1153,20 @@ namespace { } } - reference_local_to_true( - *f.gravityFluxFes, total_local, reference.total_action - ); - reference_local_to_true( - *f.gravityFluxFes, stellar_local, reference.stellar_action - ); - reference_local_to_true( - *f.gravityFluxFes, vacuum_local, reference.vacuum_action - ); + reference_local_to_true(*f.gravityFluxFes, total_local, reference.total_action); + reference_local_to_true(*f.gravityFluxFes, stellar_local, reference.stellar_action); + reference_local_to_true(*f.gravityFluxFes, vacuum_local, reference.vacuum_action); MPI_Comm communicator = f.gravityFluxFes->GetComm(); const long long local_counts[4]{ - reference.stellar_elements, reference.vacuum_elements, - reference.stellar_quadrature_points, + reference.stellar_elements, reference.vacuum_elements, reference.stellar_quadrature_points, reference.vacuum_quadrature_points }; long long global_counts[4]{}; - MPI_Allreduce( - local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator - ); + MPI_Allreduce(local_counts, global_counts, 4, MPI_LONG_LONG, MPI_SUM, communicator); reference.stellar_elements = global_counts[0]; reference.vacuum_elements = global_counts[1]; @@ -1431,28 +1190,22 @@ TEST_CASE( const blocks::form_layout layout = make_gravity_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); mfem::Vector residual; state = 0.0; - constexpr operators::context::gravity_field::GravityFieldRevisions - revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + constexpr operators::context::gravity_field::GravityFieldRevisions revisions{ + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} + }; gravity_operator.Prepare(state, revisions); gravity_operator.Mult(state, residual); @@ -1460,70 +1213,45 @@ TEST_CASE( REQUIRE(residual.Size() == layout.residual_offsets().Last()); CHECK_THAT(residual.Norml2(), WithinAbs(0.0, 1.0e-14)); - const mfem::Vector gravity_potential = - make_test_vector(layout.size(gravity_potential_block), 0.31); - set_block( - state, layout.value_offsets(), gravity_potential_block, - gravity_potential - ); + const mfem::Vector gravity_potential = make_test_vector(layout.size(gravity_potential_block), 0.31); + set_block(state, layout.value_offsets(), gravity_potential_block, gravity_potential); gravity_operator.Mult(state, residual); - const mfem::Vector gradient_residual = get_block( - residual, layout.residual_offsets(), gravity_gradient_residual_block - ); - const mfem::Vector poisson_residual = get_block( - residual, layout.residual_offsets(), gravity_poisson_residual_block - ); + const mfem::Vector gradient_residual = + get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block); + const mfem::Vector poisson_residual = + get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block); - mfem::Vector expected_gradient_residual( - layout.size(gravity_gradient_residual_block) - ); + mfem::Vector expected_gradient_residual(layout.size(gravity_gradient_residual_block)); f.gravityContext.BT->Mult(gravity_potential, expected_gradient_residual); - CHECK_THAT( - relative_difference(gradient_residual, expected_gradient_residual), - WithinAbs(0.0, 1.0e-13) - ); + CHECK_THAT(relative_difference(gradient_residual, expected_gradient_residual), WithinAbs(0.0, 1.0e-13)); CHECK_THAT(poisson_residual.Norml2(), WithinAbs(0.0, 1.0e-14)); - state = 0.0; + state = 0.0; - const mfem::Vector gravity_gradient = - make_test_vector(layout.size(gravity_gradient_block), 0.73); - set_block( - state, layout.value_offsets(), gravity_gradient_block, gravity_gradient - ); + const mfem::Vector gravity_gradient = make_test_vector(layout.size(gravity_gradient_block), 0.73); + set_block(state, layout.value_offsets(), gravity_gradient_block, gravity_gradient); gravity_operator.Mult(state, residual); - const mfem::Vector gradient_only_residual = get_block( - residual, layout.residual_offsets(), gravity_gradient_residual_block - ); - const mfem::Vector poisson_only_residual = get_block( - residual, layout.residual_offsets(), gravity_poisson_residual_block - ); + const mfem::Vector gradient_only_residual = + get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block); + const mfem::Vector poisson_only_residual = + get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block); - mfem::Vector expected_poisson_residual( - layout.size(gravity_poisson_residual_block) - ); + mfem::Vector expected_poisson_residual(layout.size(gravity_poisson_residual_block)); f.gravityContext.b_form->Mult(gravity_gradient, expected_poisson_residual); CHECK(gradient_only_residual.Norml2() > 0.0); - CHECK_THAT( - relative_difference(poisson_only_residual, expected_poisson_residual), - WithinAbs(0.0, 1.0e-13) - ); + CHECK_THAT(relative_difference(poisson_only_residual, expected_poisson_residual), WithinAbs(0.0, 1.0e-13)); - const double adjoint_lhs = gravity_gradient * expected_gradient_residual; - const double adjoint_rhs = gravity_potential * expected_poisson_residual; - const double adjoint_scale = - std::max({std::abs(adjoint_lhs), std::abs(adjoint_rhs), 1.0e-14}); + const double adjoint_lhs = gravity_gradient * expected_gradient_residual; + const double adjoint_rhs = gravity_potential * expected_poisson_residual; + const double adjoint_scale = std::max({std::abs(adjoint_lhs), std::abs(adjoint_rhs), 1.0e-14}); - CHECK_THAT( - std::abs(adjoint_lhs - adjoint_rhs) / adjoint_scale, - WithinAbs(0.0, 1.0e-12) - ); + CHECK_THAT(std::abs(adjoint_lhs - adjoint_rhs) / adjoint_scale, WithinAbs(0.0, 1.0e-12)); } TEST_CASE( @@ -1538,28 +1266,22 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); const blocks::form_layout layout = make_gravity_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); mfem::Vector residual; state = 0.0; - constexpr operators::context::gravity_field::GravityFieldRevisions - revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + constexpr operators::context::gravity_field::GravityFieldRevisions revisions{ + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} + }; const mfem::Vector density = make_constant_density(f, 1.0); set_block(state, layout.value_offsets(), density_block, density); @@ -1567,12 +1289,10 @@ TEST_CASE( gravity_operator.Prepare(state, revisions); gravity_operator.Mult(state, residual); - const mfem::Vector gradient_residual = get_block( - residual, layout.residual_offsets(), gravity_gradient_residual_block - ); - const mfem::Vector poisson_residual = get_block( - residual, layout.residual_offsets(), gravity_poisson_residual_block - ); + const mfem::Vector gradient_residual = + get_block(residual, layout.residual_offsets(), gravity_gradient_residual_block); + const mfem::Vector poisson_residual = + get_block(residual, layout.residual_offsets(), gravity_poisson_residual_block); CHECK_THAT(gradient_residual.Norml2(), WithinAbs(0.0, 1.0e-14)); CHECK(poisson_residual.Norml2() > 0.0); @@ -1586,9 +1306,7 @@ TEST_CASE( mfem::Vector doubled_state(state); mfem::Vector doubled_density(density); doubled_density *= 2.0; - set_block( - doubled_state, layout.value_offsets(), density_block, doubled_density - ); + set_block(doubled_state, layout.value_offsets(), density_block, doubled_density); mfem::Vector doubled_residual; gravity_operator.Mult(doubled_state, doubled_residual); @@ -1596,10 +1314,7 @@ TEST_CASE( mfem::Vector expected_doubled_residual(residual); expected_doubled_residual *= 2.0; - CHECK_THAT( - relative_difference(doubled_residual, expected_doubled_residual), - WithinAbs(0.0, 1.0e-12) - ); + CHECK_THAT(relative_difference(doubled_residual, expected_doubled_residual), WithinAbs(0.0, 1.0e-12)); } TEST_CASE( @@ -1614,15 +1329,14 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); const blocks::form_layout layout = make_gravity_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); @@ -1630,11 +1344,7 @@ TEST_CASE( state = 0.0; const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; const mfem::Vector vacuum_density = make_vacuum_density(f, 1.0); @@ -1662,22 +1372,19 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); const blocks::form_layout layout = make_gravity_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - const mfem::Vector displacement = make_displacement(f); - const mfem::Vector gravity_gradient_a = - make_test_vector(layout.size(gravity_gradient_block), 0.27); - const mfem::Vector gravity_gradient_b = - make_test_vector(layout.size(gravity_gradient_block), 1.13); + const mfem::Vector displacement = make_displacement(f); + const mfem::Vector gravity_gradient_a = make_test_vector(layout.size(gravity_gradient_block), 0.27); + const mfem::Vector gravity_gradient_b = make_test_vector(layout.size(gravity_gradient_block), 1.13); mfem::Vector state_a(layout.value_offsets().Last()); mfem::Vector state_b(layout.value_offsets().Last()); @@ -1687,27 +1394,13 @@ TEST_CASE( state_a = 0.0; state_b = 0.0; - set_block( - state_a, layout.value_offsets(), displacement_block, displacement - ); - set_block( - state_a, layout.value_offsets(), gravity_gradient_block, - gravity_gradient_a - ); - set_block( - state_b, layout.value_offsets(), displacement_block, displacement - ); - set_block( - state_b, layout.value_offsets(), gravity_gradient_block, - gravity_gradient_b - ); + set_block(state_a, layout.value_offsets(), displacement_block, displacement); + set_block(state_a, layout.value_offsets(), gravity_gradient_block, gravity_gradient_a); + set_block(state_b, layout.value_offsets(), displacement_block, displacement); + set_block(state_b, layout.value_offsets(), gravity_gradient_block, gravity_gradient_b); const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(state_a, revisions); @@ -1715,19 +1408,16 @@ TEST_CASE( gravity_operator.Mult(state_a, residual_a); gravity_operator.Mult(state_b, residual_b); - const mfem::Vector mass_action_a = get_block( - residual_a, layout.residual_offsets(), gravity_gradient_residual_block - ); - const mfem::Vector mass_action_b = get_block( - residual_b, layout.residual_offsets(), gravity_gradient_residual_block - ); + const mfem::Vector mass_action_a = + get_block(residual_a, layout.residual_offsets(), gravity_gradient_residual_block); + const mfem::Vector mass_action_b = + get_block(residual_b, layout.residual_offsets(), gravity_gradient_residual_block); - const double energy_a = gravity_gradient_a * mass_action_a; - const double energy_b = gravity_gradient_b * mass_action_b; - const double cross_ab = gravity_gradient_a * mass_action_b; - const double cross_ba = gravity_gradient_b * mass_action_a; - const double symmetry_scale = - std::max({std::abs(cross_ab), std::abs(cross_ba), 1.0e-14}); + const double energy_a = gravity_gradient_a * mass_action_a; + const double energy_b = gravity_gradient_b * mass_action_b; + const double cross_ab = gravity_gradient_a * mass_action_b; + const double cross_ba = gravity_gradient_b * mass_action_a; + const double symmetry_scale = std::max({std::abs(cross_ab), std::abs(cross_ba), 1.0e-14}); INFO("Mapped H(div) energy A = " << energy_a); INFO("Mapped H(div) energy B = " << energy_b); @@ -1738,9 +1428,7 @@ TEST_CASE( CHECK(std::isfinite(energy_b)); CHECK(energy_a > 0.0); CHECK(energy_b > 0.0); - CHECK_THAT( - std::abs(cross_ab - cross_ba) / symmetry_scale, WithinAbs(0.0, 1.0e-11) - ); + CHECK_THAT(std::abs(cross_ab - cross_ba) / symmetry_scale, WithinAbs(0.0, 1.0e-11)); } TEST_CASE( @@ -1755,23 +1443,20 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); const blocks::form_layout layout = make_gravity_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - const mfem::Vector displacement = make_displacement(f); - const mfem::Vector density = make_constant_density(f, 0.73); - const mfem::Vector gravity_gradient = - make_test_vector(layout.size(gravity_gradient_block), 0.41); - const mfem::Vector gravity_potential = - make_test_vector(layout.size(gravity_potential_block), 0.89); + const mfem::Vector displacement = make_displacement(f); + const mfem::Vector density = make_constant_density(f, 0.73); + const mfem::Vector gravity_gradient = make_test_vector(layout.size(gravity_gradient_block), 0.41); + const mfem::Vector gravity_potential = make_test_vector(layout.size(gravity_potential_block), 0.89); mfem::Vector displacement_state(layout.value_offsets().Last()); mfem::Vector density_state(layout.value_offsets().Last()); @@ -1785,45 +1470,21 @@ TEST_CASE( potential_state = 0.0; complete_state = 0.0; - set_block( - displacement_state, layout.value_offsets(), displacement_block, - displacement - ); + set_block(displacement_state, layout.value_offsets(), displacement_block, displacement); - set_block( - density_state, layout.value_offsets(), displacement_block, displacement - ); + set_block(density_state, layout.value_offsets(), displacement_block, displacement); set_block(density_state, layout.value_offsets(), density_block, density); - set_block( - gradient_state, layout.value_offsets(), displacement_block, displacement - ); - set_block( - gradient_state, layout.value_offsets(), gravity_gradient_block, - gravity_gradient - ); + set_block(gradient_state, layout.value_offsets(), displacement_block, displacement); + set_block(gradient_state, layout.value_offsets(), gravity_gradient_block, gravity_gradient); - set_block( - potential_state, layout.value_offsets(), displacement_block, - displacement - ); - set_block( - potential_state, layout.value_offsets(), gravity_potential_block, - gravity_potential - ); + set_block(potential_state, layout.value_offsets(), displacement_block, displacement); + set_block(potential_state, layout.value_offsets(), gravity_potential_block, gravity_potential); - set_block( - complete_state, layout.value_offsets(), displacement_block, displacement - ); + set_block(complete_state, layout.value_offsets(), displacement_block, displacement); set_block(complete_state, layout.value_offsets(), density_block, density); - set_block( - complete_state, layout.value_offsets(), gravity_gradient_block, - gravity_gradient - ); - set_block( - complete_state, layout.value_offsets(), gravity_potential_block, - gravity_potential - ); + set_block(complete_state, layout.value_offsets(), gravity_gradient_block, gravity_gradient); + set_block(complete_state, layout.value_offsets(), gravity_potential_block, gravity_potential); mfem::Vector displacement_residual; mfem::Vector density_residual; @@ -1831,11 +1492,7 @@ TEST_CASE( mfem::Vector potential_residual; mfem::Vector complete_residual; const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(complete_state, revisions); @@ -1852,16 +1509,12 @@ TEST_CASE( additive_residual += gradient_residual; additive_residual += potential_residual; - CHECK_THAT( - relative_difference(complete_residual, additive_residual), - WithinAbs(0.0, 1.0e-11) - ); + CHECK_THAT(relative_difference(complete_residual, additive_residual), WithinAbs(0.0, 1.0e-11)); } TEST_CASE( "Gravity Field Operator Stellar Hdiv Mass Matches Legacy Assembled Mass", - tags::integration &tags::solver &tags::gravity &tags::mfem_operators - &tags::legacy_comparison + tags::integration &tags::solver &tags::gravity &tags::mfem_operators &tags::legacy_comparison ) { constexpr double parity_tolerance = 1.0e-10; constexpr double vacuum_tolerance = 1.0e-13; @@ -1872,9 +1525,8 @@ TEST_CASE( REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr); - const blocks::form_layout layout = make_gravity_layout(f); - const mfem::Vector gravity_gradient = - make_core_supported_gravity_gradient(f); + const blocks::form_layout layout = make_gravity_layout(f); + const mfem::Vector gravity_gradient = make_core_supported_gravity_gradient(f); const mfem::Vector deformed_displacement = make_displacement(f); MPI_Comm communicator = f.gravityFluxFes->GetComm(); @@ -1888,34 +1540,24 @@ TEST_CASE( mfem::Vector element_values; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - if (f.mesh->GetAttribute(element_id) != - f.domainMapperStateless->GetVacuumElementAttribute()) + if (f.mesh->GetAttribute(element_id) != f.domainMapperStateless->GetVacuumElementAttribute()) continue; f.gravityFluxFes->GetElementVDofs(element_id, element_vdofs); local_gravity_gradient.GetSubVector(element_vdofs, element_values); if (element_values.Size() > 0) - local_maximum_vacuum_dof = - std::max(local_maximum_vacuum_dof, element_values.Normlinf()); + local_maximum_vacuum_dof = std::max(local_maximum_vacuum_dof, element_values.Normlinf()); } double global_maximum_vacuum_dof = 0.0; - MPI_Allreduce( - &local_maximum_vacuum_dof, &global_maximum_vacuum_dof, 1, MPI_DOUBLE, - MPI_MAX, communicator - ); + MPI_Allreduce(&local_maximum_vacuum_dof, &global_maximum_vacuum_dof, 1, MPI_DOUBLE, MPI_MAX, communicator); - INFO( - "Maximum gravity-gradient DOF on vacuum elements = " - << global_maximum_vacuum_dof - ); + INFO("Maximum gravity-gradient DOF on vacuum elements = " << global_maximum_vacuum_dof); REQUIRE_THAT(global_maximum_vacuum_dof, WithinAbs(0.0, vacuum_tolerance)); for (const bool use_deformation : std::array{false, true}) { - DYNAMIC_SECTION( - "Geometry = " << (use_deformation ? "deformed" : "identity") - ) { + DYNAMIC_SECTION("Geometry = " << (use_deformation ? "deformed" : "identity")) { mfem::Vector displacement(layout.size(displacement_block)); displacement = 0.0; if (use_deformation) @@ -1931,56 +1573,44 @@ TEST_CASE( REQUIRE(f.gravityContext.b_form != nullptr); REQUIRE(f.gravityContext.BT != nullptr); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); mfem::Vector matrix_free_residual; state = 0.0; - set_block( - state, layout.value_offsets(), displacement_block, displacement - ); - set_block( - state, layout.value_offsets(), gravity_gradient_block, - gravity_gradient - ); + set_block(state, layout.value_offsets(), displacement_block, displacement); + set_block(state, layout.value_offsets(), gravity_gradient_block, gravity_gradient); - constexpr operators::context::gravity_field::GravityFieldRevisions - revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + constexpr operators::context::gravity_field::GravityFieldRevisions revisions{ + .discretization = {1}, + .displacement = {1}, + .density = {1}, + .gravity_gradient = {1}, + .gravity_potential = {1} + }; gravity_operator.Prepare(state, revisions); gravity_operator.Mult(state, matrix_free_residual); - const mfem::Vector matrix_free_mass_action = get_block( - matrix_free_residual, layout.residual_offsets(), - gravity_gradient_residual_block - ); + const mfem::Vector matrix_free_mass_action = + get_block(matrix_free_residual, layout.residual_offsets(), gravity_gradient_residual_block); mfem::Vector legacy_mass_action(f.gravityFluxFes->GetTrueVSize()); f.gravityContext.m_form->Mult(gravity_gradient, legacy_mass_action); - const double matrix_free_mass_norm = - global_norm(matrix_free_mass_action, communicator); - const double legacy_mass_norm = - global_norm(legacy_mass_action, communicator); - const double mass_error = global_relative_difference( - matrix_free_mass_action, legacy_mass_action, communicator - ); + const double matrix_free_mass_norm = global_norm(matrix_free_mass_action, communicator); + const double legacy_mass_norm = global_norm(legacy_mass_action, communicator); + const double mass_error = + global_relative_difference(matrix_free_mass_action, legacy_mass_action, communicator); mfem::Vector mass_difference(matrix_free_mass_action); mass_difference -= legacy_mass_action; @@ -1988,10 +1618,7 @@ TEST_CASE( INFO("Geometry = " << (use_deformation ? "deformed" : "identity")); INFO("Matrix-free stellar mass norm = " << matrix_free_mass_norm); INFO("Legacy stellar mass norm = " << legacy_mass_norm); - INFO( - "Absolute stellar mass difference norm = " - << global_norm(mass_difference, communicator) - ); + INFO("Absolute stellar mass difference norm = " << global_norm(mass_difference, communicator)); INFO("Relative stellar mass-action error = " << mass_error); REQUIRE(matrix_free_mass_norm > 0.0); @@ -2014,69 +1641,58 @@ TEST_CASE( physics::update_stiffness_matrix(fem); - constexpr auto displacement_block = - mean_field::utils::blocks::get_value_block( - blocks::displacement_field.geometry_term - ); + constexpr auto displacement_block = mean_field::utils::blocks::get_value_block( + blocks::displacement_field.geometry_term + ); constexpr auto gravity_gradient_block = - mean_field::utils::blocks::get_value_block( - blocks::gravity_field.gradient_term - ); + mean_field::utils::blocks::get_value_block(blocks::gravity_field.gradient_term); constexpr auto gravity_gradient_residual_block = - mean_field::utils::blocks::get_residual_block< - blocks::gravity_field_form>(blocks::gravity_field.gradient_term); + mean_field::utils::blocks::get_residual_block(blocks::gravity_field.gradient_term); - const blocks::form_layout layout = - make_gravity_layout(fem); + const blocks::form_layout layout = make_gravity_layout(fem); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(fem, *fem.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + fem, *fem.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - fem, *fem.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - fem, *fem.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - const MPI_Comm communicator = fem.gravityFluxFes->GetComm(); + const MPI_Comm communicator = fem.gravityFluxFes->GetComm(); - const mfem::Vector gravity_gradient_true = - make_full_support_gravity_gradient(fem); + const mfem::Vector gravity_gradient_true = make_full_support_gravity_gradient(fem); for (const bool deformed : {false, true}) { DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) { CAPTURE(deformed); - const mfem::Vector displacement_true = - make_stateless_reference_displacement(fem, deformed); + const mfem::Vector displacement_true = make_stateless_reference_displacement(fem, deformed); mfem::Vector state(layout.value_offsets().Last()); state = 0.0; for (int i = 0; i < gravity_gradient_true.Size(); ++i) { - state(layout.offset(gravity_gradient_block) + i) = - gravity_gradient_true(i); + state(layout.offset(gravity_gradient_block) + i) = gravity_gradient_true(i); } for (int i = 0; i < displacement_true.Size(); ++i) { - state(layout.offset(displacement_block) + i) = - displacement_true(i); + state(layout.offset(displacement_block) + i) = displacement_true(i); } - const operators::context::gravity_field::GravityFieldRevisions - revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + const operators::context::gravity_field::GravityFieldRevisions revisions{ + .discretization = {1}, + .displacement = {1}, + .density = {1}, + .gravity_gradient = {1}, + .gravity_potential = {1} + }; gravity_operator.Prepare(state, revisions); @@ -2084,20 +1700,14 @@ TEST_CASE( gravity_operator.Mult(state, residual); - mfem::Vector operator_action( - layout.size(gravity_gradient_residual_block) - ); + mfem::Vector operator_action(layout.size(gravity_gradient_residual_block)); for (int i = 0; i < operator_action.Size(); ++i) { - operator_action(i) = residual( - layout.offset(gravity_gradient_residual_block) + i - ); + operator_action(i) = residual(layout.offset(gravity_gradient_residual_block) + i); } const StatelessHDivMassReference reference = - evaluate_stateless_hdiv_mass_quadrature_reference( - fem, gravity_gradient_true, displacement_true - ); + evaluate_stateless_hdiv_mass_quadrature_reference(fem, gravity_gradient_true, displacement_true); mfem::Vector decomposed_reference(reference.stellar_action); @@ -2107,71 +1717,48 @@ TEST_CASE( action_difference -= reference.total_action; - const double operator_norm = - global_vector_norm(operator_action, communicator); + const double operator_norm = global_vector_norm(operator_action, communicator); - const double reference_norm = - global_vector_norm(reference.total_action, communicator); + const double reference_norm = global_vector_norm(reference.total_action, communicator); - const double stellar_action_norm = - global_vector_norm(reference.stellar_action, communicator); + const double stellar_action_norm = global_vector_norm(reference.stellar_action, communicator); - const double vacuum_action_norm = - global_vector_norm(reference.vacuum_action, communicator); + const double vacuum_action_norm = global_vector_norm(reference.vacuum_action, communicator); - const double absolute_action_error = - global_vector_norm(action_difference, communicator); + const double absolute_action_error = global_vector_norm(action_difference, communicator); - const double relative_action_error = global_relative_vector_error( - operator_action, reference.total_action, communicator - ); + const double relative_action_error = + global_relative_vector_error(operator_action, reference.total_action, communicator); - const double decomposition_error = global_relative_vector_error( - reference.total_action, decomposed_reference, communicator - ); + const double decomposition_error = + global_relative_vector_error(reference.total_action, decomposed_reference, communicator); - const double operator_energy = global_vector_dot( - gravity_gradient_true, operator_action, communicator - ); + const double operator_energy = global_vector_dot(gravity_gradient_true, operator_action, communicator); - const double reference_energy = global_vector_dot( - gravity_gradient_true, reference.total_action, communicator - ); + const double reference_energy = + global_vector_dot(gravity_gradient_true, reference.total_action, communicator); - const double stellar_energy = global_vector_dot( - gravity_gradient_true, reference.stellar_action, communicator - ); + const double stellar_energy = + global_vector_dot(gravity_gradient_true, reference.stellar_action, communicator); - const double vacuum_energy = global_vector_dot( - gravity_gradient_true, reference.vacuum_action, communicator - ); + const double vacuum_energy = + global_vector_dot(gravity_gradient_true, reference.vacuum_action, communicator); const double relative_energy_error = std::abs(operator_energy - reference_energy) / - std::max( - std::abs(reference_energy), - std::numeric_limits::epsilon() - ); + std::max(std::abs(reference_energy), std::numeric_limits::epsilon()); const double vacuum_action_fraction = - vacuum_action_norm / - std::max( - reference_norm, std::numeric_limits::epsilon() - ); + vacuum_action_norm / std::max(reference_norm, std::numeric_limits::epsilon()); - const double vacuum_energy_fraction = - vacuum_energy / reference_energy; + const double vacuum_energy_fraction = vacuum_energy / reference_energy; - const double stellar_vacuum_dot = global_vector_dot( - reference.stellar_action, reference.vacuum_action, communicator - ); + const double stellar_vacuum_dot = + global_vector_dot(reference.stellar_action, reference.vacuum_action, communicator); const double stellar_vacuum_alignment = stellar_vacuum_dot / - std::max( - stellar_action_norm * vacuum_action_norm, - std::numeric_limits::epsilon() - ); + std::max(stellar_action_norm * vacuum_action_norm, std::numeric_limits::epsilon()); INFO("Geometry = " << (deformed ? "deformed" : "identity")); @@ -2179,26 +1766,18 @@ TEST_CASE( INFO("Vacuum elements = " << reference.vacuum_elements); + INFO("Stellar quadrature points = " << reference.stellar_quadrature_points); + + INFO("Vacuum quadrature points = " << reference.vacuum_quadrature_points); + INFO( - "Stellar quadrature points = " - << reference.stellar_quadrature_points + "Stellar mapping determinant range = [" << reference.minimum_stellar_determinant << ", " + << reference.maximum_stellar_determinant << "]" ); INFO( - "Vacuum quadrature points = " - << reference.vacuum_quadrature_points - ); - - INFO( - "Stellar mapping determinant range = [" - << reference.minimum_stellar_determinant << ", " - << reference.maximum_stellar_determinant << "]" - ); - - INFO( - "Vacuum mapping determinant range = [" - << reference.minimum_vacuum_determinant << ", " - << reference.maximum_vacuum_determinant << "]" + "Vacuum mapping determinant range = [" << reference.minimum_vacuum_determinant << ", " + << reference.maximum_vacuum_determinant << "]" ); INFO("Operator mass-action norm = " << operator_norm); @@ -2211,9 +1790,7 @@ TEST_CASE( INFO("Vacuum action fraction = " << vacuum_action_fraction); - INFO( - "Stellar-vacuum action alignment = " << stellar_vacuum_alignment - ); + INFO("Stellar-vacuum action alignment = " << stellar_vacuum_alignment); INFO("Absolute action error = " << absolute_action_error); @@ -2257,20 +1834,11 @@ TEST_CASE( constexpr double energy_tolerance = 1.0e-11; - CHECK_THAT( - decomposition_error, - Catch::Matchers::WithinAbs(0.0, decomposition_tolerance) - ); + CHECK_THAT(decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance)); - CHECK_THAT( - relative_action_error, - Catch::Matchers::WithinAbs(0.0, action_tolerance) - ); + CHECK_THAT(relative_action_error, Catch::Matchers::WithinAbs(0.0, action_tolerance)); - CHECK_THAT( - relative_energy_error, - Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); + CHECK_THAT(relative_energy_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); } } } @@ -2286,30 +1854,24 @@ TEST_CASE( physics::update_stiffness_matrix(f); const gravity_layout layout = make_gravity_jacobian_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); - state = 0.0; + state = 0.0; - const mfem::Vector displacement = - make_stateless_reference_displacement(f, true); + const mfem::Vector displacement = make_stateless_reference_displacement(f, true); set_value_block(state, layout, displacement_block, displacement); const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(state, revisions); @@ -2326,14 +1888,8 @@ TEST_CASE( std::vector cases; cases.push_back({"density", make_density_direction(layout), false, true}); - cases.push_back( - {"gravity gradient", make_gravity_gradient_direction(layout), true, - true} - ); - cases.push_back( - {"gravity potential", make_gravity_potential_direction(layout), true, - false} - ); + cases.push_back({"gravity gradient", make_gravity_gradient_direction(layout), true, true}); + cases.push_back({"gravity potential", make_gravity_potential_direction(layout), true, false}); for (const DirectionCase &direction_case : cases) { DYNAMIC_SECTION("Direction = " << direction_case.name) { @@ -2342,35 +1898,22 @@ TEST_CASE( mfem::Vector jacobian_action; gradient.Mult(direction_case.direction, jacobian_action); - const mfem::Vector finite_difference = evaluate_centered_difference( - gravity_operator, state, direction_case.direction, - difference_step - ); + const mfem::Vector finite_difference = + evaluate_centered_difference(gravity_operator, state, direction_case.direction, difference_step); - const mfem::Vector gradient_action = get_residual_block( - jacobian_action, layout, gravity_gradient_residual_block - ); - const mfem::Vector poisson_action = get_residual_block( - jacobian_action, layout, gravity_poisson_residual_block - ); + const mfem::Vector gradient_action = + get_residual_block(jacobian_action, layout, gravity_gradient_residual_block); + const mfem::Vector poisson_action = + get_residual_block(jacobian_action, layout, gravity_poisson_residual_block); - const double relative_error = global_relative_vector_error( - jacobian_action, finite_difference, communicator - ); - const double gradient_norm = - global_vector_norm(gradient_action, communicator); - const double poisson_norm = - global_vector_norm(poisson_action, communicator); + const double relative_error = + global_relative_vector_error(jacobian_action, finite_difference, communicator); + const double gradient_norm = global_vector_norm(gradient_action, communicator); + const double poisson_norm = global_vector_norm(poisson_action, communicator); INFO("Direction = " << direction_case.name); - INFO( - "Jacobian action norm = " - << global_vector_norm(jacobian_action, communicator) - ); - INFO( - "Finite-difference action norm = " - << global_vector_norm(finite_difference, communicator) - ); + INFO("Jacobian action norm = " << global_vector_norm(jacobian_action, communicator)); + INFO("Finite-difference action norm = " << global_vector_norm(finite_difference, communicator)); INFO("Gradient residual action norm = " << gradient_norm); INFO("Poisson residual action norm = " << poisson_norm); INFO("Relative Jacobian error = " << relative_error); @@ -2378,27 +1921,18 @@ TEST_CASE( constexpr double jacobian_tolerance = 2.0e-8; constexpr double zero_tolerance = 1.0e-13; - CHECK_THAT( - relative_error, - Catch::Matchers::WithinAbs(0.0, jacobian_tolerance) - ); + CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, jacobian_tolerance)); if (direction_case.expect_gradient_residual) { CHECK(gradient_norm > zero_tolerance); } else { - CHECK_THAT( - gradient_norm, - Catch::Matchers::WithinAbs(0.0, zero_tolerance) - ); + CHECK_THAT(gradient_norm, Catch::Matchers::WithinAbs(0.0, zero_tolerance)); } if (direction_case.expect_poisson_residual) { CHECK(poisson_norm > zero_tolerance); } else { - CHECK_THAT( - poisson_norm, - Catch::Matchers::WithinAbs(0.0, zero_tolerance) - ); + CHECK_THAT(poisson_norm, Catch::Matchers::WithinAbs(0.0, zero_tolerance)); } } } @@ -2415,27 +1949,21 @@ TEST_CASE( physics::update_stiffness_matrix(f); const gravity_layout layout = make_gravity_jacobian_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); - const mfem::Vector direction = - make_combined_fixed_geometry_direction(layout); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); + const mfem::Vector direction = make_combined_fixed_geometry_direction(layout); operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(state, revisions); @@ -2447,13 +1975,9 @@ TEST_CASE( double best_error = std::numeric_limits::infinity(); for (const double step : std::array{1.0e-4, 1.0e-6, 1.0e-7}) { - const mfem::Vector finite_difference = evaluate_centered_difference( - gravity_operator, state, direction, step - ); - const double relative_error = global_relative_vector_error( - jacobian_action, finite_difference, communicator - ); - best_error = std::min(best_error, relative_error); + const mfem::Vector finite_difference = evaluate_centered_difference(gravity_operator, state, direction, step); + const double relative_error = global_relative_vector_error(jacobian_action, finite_difference, communicator); + best_error = std::min(best_error, relative_error); INFO("Centered-difference step = " << step); INFO("Relative Jacobian error = " << relative_error); @@ -2476,27 +2000,22 @@ TEST_CASE( physics::update_stiffness_matrix(f); const gravity_layout layout = make_gravity_jacobian_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - const mfem::Vector state = make_gravity_jacobian_state(f, layout, false); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, false); const mfem::Vector direction_a = make_density_direction(layout); const mfem::Vector direction_b = make_gravity_gradient_direction(layout); const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(state, revisions); @@ -2522,9 +2041,7 @@ TEST_CASE( expected_action.Add(scale_b, action_b); MPI_Comm communicator = f.mesh->GetComm(); - const double relative_error = global_relative_vector_error( - combined_action, expected_action, communicator - ); + const double relative_error = global_relative_vector_error(combined_action, expected_action, communicator); INFO("Relative Jacobian linearity error = " << relative_error); CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, 2.0e-13)); @@ -2540,34 +2057,25 @@ TEST_CASE( physics::update_stiffness_matrix(f); const gravity_layout layout = make_gravity_jacobian_layout(f); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state_a = make_gravity_jacobian_state(f, layout, false); const mfem::Vector state_b = make_gravity_jacobian_state(f, layout, true); const operators::context::gravity_field::GravityFieldRevisions revisions_a{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; const operators::context::gravity_field::GravityFieldRevisions revisions_b{ - .discretization = {1}, - .displacement = {2}, - .density = {2}, - .gravity_gradient = {2}, - .gravity_potential = {2} + .discretization = {1}, .displacement = {2}, .density = {2}, .gravity_gradient = {2}, .gravity_potential = {2} }; MPI_Comm communicator = f.mesh->GetComm(); @@ -2575,13 +2083,9 @@ TEST_CASE( gravity_operator.Prepare(state_a, revisions_a); mfem::Operator &returned_gradient_a = gravity_operator.GetGradient(state_a); - REQUIRE( - &returned_gradient_a == static_cast(&gravity_jacobian) - ); + REQUIRE(&returned_gradient_a == static_cast(&gravity_jacobian)); - check_linearization_context_matches_state( - linearization_context, state_a, layout.value_offsets(), communicator - ); + check_linearization_context_matches_state(linearization_context, state_a, layout.value_offsets(), communicator); state_a = 0.0; @@ -2589,26 +2093,17 @@ TEST_CASE( const double stored_state_norm = global_vector_norm(linearization_context.GetDensity(), communicator) + - global_vector_norm( - linearization_context.GetGeometryContext().GetDisplacement(), - communicator - ) + - global_vector_norm( - linearization_context.GetGravityGradient(), communicator - ); + global_vector_norm(linearization_context.GetGeometryContext().GetDisplacement(), communicator) + + global_vector_norm(linearization_context.GetGravityGradient(), communicator); CHECK(stored_state_norm > 0.0); gravity_operator.Prepare(state_b, revisions_b); mfem::Operator &returned_gradient_b = gravity_operator.GetGradient(state_b); - REQUIRE( - &returned_gradient_b == static_cast(&gravity_jacobian) - ); + REQUIRE(&returned_gradient_b == static_cast(&gravity_jacobian)); - check_linearization_context_matches_state( - linearization_context, state_b, layout.value_offsets(), communicator - ); + check_linearization_context_matches_state(linearization_context, state_b, layout.value_offsets(), communicator); } TEST_CASE( "Mapped Hdiv Mass Variation Is Linear In Displacement Direction", @@ -2619,11 +2114,9 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); mfem::Vector zero_direction(f.displacementFes->GetTrueVSize()); mfem::Vector zero_gravity_gradient(f.gravityFluxFes->GetTrueVSize()); @@ -2632,26 +2125,19 @@ TEST_CASE( mfem::Vector zero_direction_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - zero_direction, zero_direction_action + f, domain_mapper, fields.gravity_gradient, fields.displacement, zero_direction, zero_direction_action ); - INFO( - "Zero-direction action norm = " - << global_norm(zero_direction_action, communicator) - ); + INFO("Zero-direction action norm = " << global_norm(zero_direction_action, communicator)); CHECK(global_norm(zero_direction_action, communicator) < 1.0e-13); mfem::Vector zero_gravity_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, zero_gravity_gradient, fields.displacement, - fields.displacement_direction_1, zero_gravity_action + f, domain_mapper, zero_gravity_gradient, fields.displacement, fields.displacement_direction_1, + zero_gravity_action ); - INFO( - "Zero-gravity action norm = " - << global_norm(zero_gravity_action, communicator) - ); + INFO("Zero-gravity action norm = " << global_norm(zero_gravity_action, communicator)); CHECK(global_norm(zero_gravity_action, communicator) < 1.0e-13); constexpr double scale_1 = 1.7; @@ -2666,31 +2152,23 @@ TEST_CASE( mfem::Vector combined_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - fields.displacement_direction_1, action_1 + f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, action_1 ); operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - fields.displacement_direction_2, action_2 + f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_2, action_2 ); operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - combined_direction, combined_action + f, domain_mapper, fields.gravity_gradient, fields.displacement, combined_direction, combined_action ); mfem::Vector expected_action(action_1); expected_action *= scale_1; expected_action.Add(scale_2, action_2); - const double linearity_error = - global_relative_error(combined_action, expected_action, communicator); + const double linearity_error = global_relative_error(combined_action, expected_action, communicator); - INFO( - "Combined action norm = " << global_norm(combined_action, communicator) - ); - INFO( - "Expected action norm = " << global_norm(expected_action, communicator) - ); + INFO("Combined action norm = " << global_norm(combined_action, communicator)); + INFO("Expected action norm = " << global_norm(expected_action, communicator)); INFO("Displacement-direction linearity error = " << linearity_error); CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 2.0e-12)); @@ -2705,105 +2183,78 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); - constexpr double difference_step = 1.0e-5; - constexpr double comparison_tolerance = 2.0e-7; + constexpr double difference_step = 1.0e-5; + constexpr double comparison_tolerance = 2.0e-7; mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize()); identity_displacement = 0.0; for (const bool deformed : std::array{false, true}) { - const mfem::Vector &base_displacement = - deformed ? fields.displacement : identity_displacement; + const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement; mfem::Vector analytic_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, base_displacement, - fields.displacement_direction_1, analytic_action + f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1, + analytic_action ); - const mfem::Vector finite_difference_action = - centered_hdiv_mass_geometry_difference( - f, domain_mapper, fields.gravity_gradient, base_displacement, - fields.displacement_direction_1, difference_step - ); - - const double relative_error = global_relative_error( - analytic_action, finite_difference_action, communicator + const mfem::Vector finite_difference_action = centered_hdiv_mass_geometry_difference( + f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1, + difference_step ); + const double relative_error = global_relative_error(analytic_action, finite_difference_action, communicator); + INFO("Geometry = " << (deformed ? "deformed" : "identity")); - INFO( - "Analytic variation norm = " - << global_norm(analytic_action, communicator) - ); - INFO( - "Finite-difference variation norm = " - << global_norm(finite_difference_action, communicator) - ); + INFO("Analytic variation norm = " << global_norm(analytic_action, communicator)); + INFO("Finite-difference variation norm = " << global_norm(finite_difference_action, communicator)); INFO("Relative H(div) mass-variation error = " << relative_error); - CHECK_THAT( - relative_error, - Catch::Matchers::WithinAbs(0.0, comparison_tolerance) - ); + CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, comparison_tolerance)); } } TEST_CASE( "Mapped Hdiv Mass Geometry Difference Converges To Analytic Variation", - tags::integration &tags::solver &tags::gravity &tags::mfem_operators - &tags::convergence + tags::integration &tags::solver &tags::gravity &tags::mfem_operators &tags::convergence ) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); mfem::Vector analytic_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - fields.displacement_direction_1, analytic_action + f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, analytic_action ); constexpr std::array difference_steps{8.0e-2, 4.0e-2, 2.0e-2}; std::array errors{}; for (std::size_t i = 0; i < difference_steps.size(); ++i) { - const mfem::Vector finite_difference_action = - centered_hdiv_mass_geometry_difference( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - fields.displacement_direction_1, difference_steps[i] - ); - - errors[i] = global_relative_error( - finite_difference_action, analytic_action, communicator + const mfem::Vector finite_difference_action = centered_hdiv_mass_geometry_difference( + f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, + difference_steps[i] ); + errors[i] = global_relative_error(finite_difference_action, analytic_action, communicator); + INFO("Difference step = " << difference_steps[i]); INFO("Relative error = " << errors[i]); } - const double first_observed_order = - std::log(errors[0] / errors[1]) / std::log(2.0); - const double second_observed_order = - std::log(errors[1] / errors[2]) / std::log(2.0); + const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0); + const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0); - INFO( - "Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] - << "]" - ); + INFO("Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]"); INFO("First observed convergence order = " << first_observed_order); INFO("Second observed convergence order = " << second_observed_order); @@ -2822,12 +2273,10 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector density = make_source_variation_density(f); - MPI_Comm communicator = f.mesh->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector density = make_source_variation_density(f); + MPI_Comm communicator = f.mesh->GetComm(); mfem::Vector zero_density(f.densityFes->GetTrueVSize()); mfem::Vector zero_direction(f.displacementFes->GetTrueVSize()); @@ -2836,24 +2285,18 @@ TEST_CASE( mfem::Vector zero_direction_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, fields.displacement, zero_direction, - zero_direction_action + f, domain_mapper, density, fields.displacement, zero_direction, zero_direction_action ); - REQUIRE( - zero_direction_action.Size() == f.gravityPotentialFes->GetTrueVSize() - ); + REQUIRE(zero_direction_action.Size() == f.gravityPotentialFes->GetTrueVSize()); CHECK(global_norm(zero_direction_action, communicator) < 1.0e-13); mfem::Vector zero_density_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, zero_density, fields.displacement, - fields.displacement_direction_1, zero_density_action + f, domain_mapper, zero_density, fields.displacement, fields.displacement_direction_1, zero_density_action ); - REQUIRE( - zero_density_action.Size() == f.gravityPotentialFes->GetTrueVSize() - ); + REQUIRE(zero_density_action.Size() == f.gravityPotentialFes->GetTrueVSize()); CHECK(global_norm(zero_density_action, communicator) < 1.0e-13); constexpr double scale_1 = 1.4; @@ -2868,16 +2311,13 @@ TEST_CASE( mfem::Vector combined_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, fields.displacement, - fields.displacement_direction_1, action_1 + f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, action_1 ); operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, fields.displacement, - fields.displacement_direction_2, action_2 + f, domain_mapper, density, fields.displacement, fields.displacement_direction_2, action_2 ); operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, fields.displacement, combined_direction, - combined_action + f, domain_mapper, density, fields.displacement, combined_direction, combined_action ); REQUIRE(action_1.Size() == f.gravityPotentialFes->GetTrueVSize()); @@ -2888,17 +2328,10 @@ TEST_CASE( expected_action *= scale_1; expected_action.Add(scale_2, action_2); - const double linearity_error = - global_relative_error(combined_action, expected_action, communicator); + const double linearity_error = global_relative_error(combined_action, expected_action, communicator); - INFO( - "Combined source-variation norm = " - << global_norm(combined_action, communicator) - ); - INFO( - "Expected source-variation norm = " - << global_norm(expected_action, communicator) - ); + INFO("Combined source-variation norm = " << global_norm(combined_action, communicator)); + INFO("Expected source-variation norm = " << global_norm(expected_action, communicator)); INFO("Source-variation linearity error = " << linearity_error); CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 2.0e-12)); @@ -2913,86 +2346,60 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector density = make_source_variation_density(f); - MPI_Comm communicator = f.mesh->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector density = make_source_variation_density(f); + MPI_Comm communicator = f.mesh->GetComm(); - constexpr double difference_step = 1.0e-5; - constexpr double comparison_tolerance = 2.0e-7; + constexpr double difference_step = 1.0e-5; + constexpr double comparison_tolerance = 2.0e-7; mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize()); identity_displacement = 0.0; for (const bool deformed : std::array{false, true}) { - const mfem::Vector &base_displacement = - deformed ? fields.displacement : identity_displacement; + const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement; mfem::Vector analytic_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, base_displacement, - fields.displacement_direction_1, analytic_action + f, domain_mapper, density, base_displacement, fields.displacement_direction_1, analytic_action ); - const mfem::Vector finite_difference_action = - centered_source_geometry_difference( - f, domain_mapper, density, base_displacement, - fields.displacement_direction_1, difference_step - ); - - REQUIRE( - analytic_action.Size() == f.gravityPotentialFes->GetTrueVSize() - ); - REQUIRE( - finite_difference_action.Size() == - f.gravityPotentialFes->GetTrueVSize() + const mfem::Vector finite_difference_action = centered_source_geometry_difference( + f, domain_mapper, density, base_displacement, fields.displacement_direction_1, difference_step ); - const double relative_error = global_relative_error( - analytic_action, finite_difference_action, communicator - ); + REQUIRE(analytic_action.Size() == f.gravityPotentialFes->GetTrueVSize()); + REQUIRE(finite_difference_action.Size() == f.gravityPotentialFes->GetTrueVSize()); + + const double relative_error = global_relative_error(analytic_action, finite_difference_action, communicator); INFO("Geometry = " << (deformed ? "deformed" : "identity")); - INFO( - "Analytic source-variation norm = " - << global_norm(analytic_action, communicator) - ); - INFO( - "Finite-difference source-variation norm = " - << global_norm(finite_difference_action, communicator) - ); + INFO("Analytic source-variation norm = " << global_norm(analytic_action, communicator)); + INFO("Finite-difference source-variation norm = " << global_norm(finite_difference_action, communicator)); INFO("Relative source-variation error = " << relative_error); - CHECK_THAT( - relative_error, - Catch::Matchers::WithinAbs(0.0, comparison_tolerance) - ); + CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, comparison_tolerance)); } } TEST_CASE( "Mapped Source Geometry Difference Converges To Analytic Variation", - tags::integration &tags::solver &tags::gravity &tags::mfem_operators - &tags::convergence + tags::integration &tags::solver &tags::gravity &tags::mfem_operators &tags::convergence ) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector density = make_source_variation_density(f); - MPI_Comm communicator = f.mesh->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector density = make_source_variation_density(f); + MPI_Comm communicator = f.mesh->GetComm(); mfem::Vector analytic_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, density, fields.displacement, - fields.displacement_direction_1, analytic_action + f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, analytic_action ); REQUIRE(analytic_action.Size() == f.gravityPotentialFes->GetTrueVSize()); @@ -3001,30 +2408,18 @@ TEST_CASE( std::array errors{}; for (std::size_t i = 0; i < difference_steps.size(); ++i) { - const mfem::Vector finite_difference_action = - centered_source_geometry_difference( - f, domain_mapper, density, fields.displacement, - fields.displacement_direction_1, difference_steps[i] - ); + const mfem::Vector finite_difference_action = centered_source_geometry_difference( + f, domain_mapper, density, fields.displacement, fields.displacement_direction_1, difference_steps[i] + ); - REQUIRE( - finite_difference_action.Size() == - f.gravityPotentialFes->GetTrueVSize() - ); - errors[i] = global_relative_error( - finite_difference_action, analytic_action, communicator - ); + REQUIRE(finite_difference_action.Size() == f.gravityPotentialFes->GetTrueVSize()); + errors[i] = global_relative_error(finite_difference_action, analytic_action, communicator); } - const double first_observed_order = - std::log(errors[0] / errors[1]) / std::log(2.0); - const double second_observed_order = - std::log(errors[1] / errors[2]) / std::log(2.0); + const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0); + const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0); - INFO( - "Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] - << "]" - ); + INFO("Errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]"); INFO("First observed convergence order = " << first_observed_order); INFO("Second observed convergence order = " << second_observed_order); @@ -3043,46 +2438,35 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector second_gravity_gradient = - make_secondary_gravity_gradient(f); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector second_gravity_gradient = make_secondary_gravity_gradient(f); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); mfem::Vector identity_displacement(f.displacementFes->GetTrueVSize()); identity_displacement = 0.0; for (const bool deformed : std::array{false, true}) { - const mfem::Vector &base_displacement = - deformed ? fields.displacement : identity_displacement; + const mfem::Vector &base_displacement = deformed ? fields.displacement : identity_displacement; mfem::Vector first_action; mfem::Vector second_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, base_displacement, - fields.displacement_direction_1, first_action + f, domain_mapper, fields.gravity_gradient, base_displacement, fields.displacement_direction_1, first_action ); operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, second_gravity_gradient, base_displacement, - fields.displacement_direction_1, second_action + f, domain_mapper, second_gravity_gradient, base_displacement, fields.displacement_direction_1, second_action ); REQUIRE(first_action.Size() == f.gravityFluxFes->GetTrueVSize()); REQUIRE(second_action.Size() == f.gravityFluxFes->GetTrueVSize()); - const double left_pairing = - global_dot(fields.gravity_gradient, second_action, communicator); - const double right_pairing = - global_dot(second_gravity_gradient, first_action, communicator); - const double symmetry_error = - std::abs(left_pairing - right_pairing) / - std::max( - {std::abs(left_pairing), std::abs(right_pairing), 1.0e-14} - ); + const double left_pairing = global_dot(fields.gravity_gradient, second_action, communicator); + const double right_pairing = global_dot(second_gravity_gradient, first_action, communicator); + const double symmetry_error = std::abs(left_pairing - right_pairing) / + std::max({std::abs(left_pairing), std::abs(right_pairing), 1.0e-14}); INFO("Geometry = " << (deformed ? "deformed" : "identity")); INFO("g1^T delta_M g2 = " << left_pairing); @@ -3102,17 +2486,14 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector second_gravity_gradient = - make_secondary_gravity_gradient(f); - const mfem::Vector first_density = make_source_variation_density(f); - const mfem::Vector second_density = make_secondary_source_density(f); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector second_gravity_gradient = make_secondary_gravity_gradient(f); + const mfem::Vector first_density = make_source_variation_density(f); + const mfem::Vector second_density = make_secondary_source_density(f); - constexpr double scale_1 = 1.3; - constexpr double scale_2 = -0.4; + constexpr double scale_1 = 1.3; + constexpr double scale_2 = -0.4; { MPI_Comm communicator = f.gravityFluxFes->GetComm(); @@ -3126,16 +2507,16 @@ TEST_CASE( mfem::Vector combined_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, fields.gravity_gradient, fields.displacement, - fields.displacement_direction_1, first_action + f, domain_mapper, fields.gravity_gradient, fields.displacement, fields.displacement_direction_1, + first_action ); operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, second_gravity_gradient, fields.displacement, - fields.displacement_direction_1, second_action + f, domain_mapper, second_gravity_gradient, fields.displacement, fields.displacement_direction_1, + second_action ); operators::kernels::apply_mapped_hdiv_mass_variation( - f, domain_mapper, combined_gravity_gradient, fields.displacement, - fields.displacement_direction_1, combined_action + f, domain_mapper, combined_gravity_gradient, fields.displacement, fields.displacement_direction_1, + combined_action ); REQUIRE(first_action.Size() == f.gravityFluxFes->GetTrueVSize()); @@ -3146,9 +2527,7 @@ TEST_CASE( expected_action *= scale_1; expected_action.Add(scale_2, second_action); - const double linearity_error = global_relative_error( - combined_action, expected_action, communicator - ); + const double linearity_error = global_relative_error(combined_action, expected_action, communicator); INFO("H(div) base-field linearity error = " << linearity_error); CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 3.0e-12)); @@ -3166,31 +2545,24 @@ TEST_CASE( mfem::Vector combined_action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, first_density, fields.displacement, - fields.displacement_direction_1, first_action + f, domain_mapper, first_density, fields.displacement, fields.displacement_direction_1, first_action ); operators::kernels::apply_mapped_source_variation( - f, domain_mapper, second_density, fields.displacement, - fields.displacement_direction_1, second_action + f, domain_mapper, second_density, fields.displacement, fields.displacement_direction_1, second_action ); operators::kernels::apply_mapped_source_variation( - f, domain_mapper, combined_density, fields.displacement, - fields.displacement_direction_1, combined_action + f, domain_mapper, combined_density, fields.displacement, fields.displacement_direction_1, combined_action ); REQUIRE(first_action.Size() == f.gravityPotentialFes->GetTrueVSize()); REQUIRE(second_action.Size() == f.gravityPotentialFes->GetTrueVSize()); - REQUIRE( - combined_action.Size() == f.gravityPotentialFes->GetTrueVSize() - ); + REQUIRE(combined_action.Size() == f.gravityPotentialFes->GetTrueVSize()); mfem::Vector expected_action(first_action); expected_action *= scale_1; expected_action.Add(scale_2, second_action); - const double linearity_error = global_relative_error( - combined_action, expected_action, communicator - ); + const double linearity_error = global_relative_error(combined_action, expected_action, communicator); INFO("Source base-field linearity error = " << linearity_error); CHECK_THAT(linearity_error, Catch::Matchers::WithinAbs(0.0, 3.0e-12)); @@ -3206,23 +2578,18 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const mapping::DomainMapperStateless &domain_mapper = - *f.domainMapperStateless; - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); - const mfem::Vector vacuum_density = - make_vacuum_only_density(f, vacuum_attribute); - MPI_Comm communicator = f.mesh->GetComm(); + const mapping::DomainMapperStateless &domain_mapper = *f.domainMapperStateless; + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const int vacuum_attribute = domain_mapper.GetVacuumElementAttribute(); + const mfem::Vector vacuum_density = make_vacuum_only_density(f, vacuum_attribute); + MPI_Comm communicator = f.mesh->GetComm(); - const double vacuum_density_norm = - global_norm(vacuum_density, communicator); + const double vacuum_density_norm = global_norm(vacuum_density, communicator); REQUIRE(vacuum_density_norm > 0.0); mfem::Vector action; operators::kernels::apply_mapped_source_variation( - f, domain_mapper, vacuum_density, fields.displacement, - fields.displacement_direction_1, action + f, domain_mapper, vacuum_density, fields.displacement, fields.displacement_direction_1, action ); REQUIRE(action.Size() == f.gravityPotentialFes->GetTrueVSize()); @@ -3247,37 +2614,28 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const gravity_layout layout = make_gravity_jacobian_layout(f); - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); - const mfem::Vector direction = - make_displacement_direction(layout, fields.displacement_direction_1); - const mfem::Vector density = get_value_block(state, layout, density_block); - const mfem::Vector displacement = - get_value_block(state, layout, displacement_block); - const mfem::Vector gravity_gradient = - get_value_block(state, layout, gravity_gradient_block); + const gravity_layout layout = make_gravity_jacobian_layout(f); + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); + const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1); + const mfem::Vector density = get_value_block(state, layout, density_block); + const mfem::Vector displacement = get_value_block(state, layout, displacement_block); + const mfem::Vector gravity_gradient = get_value_block(state, layout, gravity_gradient_block); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); const operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; gravity_operator.Prepare(state, revisions); @@ -3287,41 +2645,26 @@ TEST_CASE( REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last()); - const mfem::Vector gradient_action = get_residual_block( - jacobian_action, layout, gravity_gradient_residual_block - ); - const mfem::Vector poisson_action = get_residual_block( - jacobian_action, layout, gravity_poisson_residual_block - ); + const mfem::Vector gradient_action = get_residual_block(jacobian_action, layout, gravity_gradient_residual_block); + const mfem::Vector poisson_action = get_residual_block(jacobian_action, layout, gravity_poisson_residual_block); mfem::Vector expected_gradient_action; mfem::Vector expected_poisson_action; operators::kernels::apply_mapped_hdiv_mass_variation( - f, *f.domainMapperStateless, gravity_gradient, displacement, - fields.displacement_direction_1, expected_gradient_action + f, *f.domainMapperStateless, gravity_gradient, displacement, fields.displacement_direction_1, + expected_gradient_action ); operators::kernels::apply_mapped_source_variation( - f, *f.domainMapperStateless, density, displacement, - fields.displacement_direction_1, expected_poisson_action + f, *f.domainMapperStateless, density, displacement, fields.displacement_direction_1, expected_poisson_action ); expected_poisson_action *= -1.0; MPI_Comm communicator = f.mesh->GetComm(); - const double gradient_error = global_relative_error( - gradient_action, expected_gradient_action, communicator - ); - const double poisson_error = global_relative_error( - poisson_action, expected_poisson_action, communicator - ); + const double gradient_error = global_relative_error(gradient_action, expected_gradient_action, communicator); + const double poisson_error = global_relative_error(poisson_action, expected_poisson_action, communicator); - INFO( - "Gradient displacement action norm = " - << global_norm(gradient_action, communicator) - ); - INFO( - "Poisson displacement action norm = " - << global_norm(poisson_action, communicator) - ); + INFO("Gradient displacement action norm = " << global_norm(gradient_action, communicator)); + INFO("Poisson displacement action norm = " << global_norm(poisson_action, communicator)); INFO("Gradient displacement-block error = " << gradient_error); INFO("Poisson displacement-block error = " << poisson_error); @@ -3344,21 +2687,18 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const gravity_layout layout = make_gravity_jacobian_layout(f); - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector direction = - make_displacement_direction(layout, fields.displacement_direction_1); + const gravity_layout layout = make_gravity_jacobian_layout(f); + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); constexpr double difference_step = 1.0e-5; @@ -3372,35 +2712,29 @@ TEST_CASE( for (const bool deformed : std::array{false, true}) { DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) { - const mfem::Vector state = - make_gravity_jacobian_state(f, layout, deformed); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, deformed); REQUIRE(state.Size() == layout.value_offsets().Last()); - const operators::context::gravity_field::GravityFieldRevisions - base_revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + const operators::context::gravity_field::GravityFieldRevisions base_revisions{ + .discretization = {1}, + .displacement = {1}, + .density = {1}, + .gravity_gradient = {1}, + .gravity_potential = {1} + }; gravity_operator.Prepare(state, base_revisions); REQUIRE(linearization_context.IsPrepared()); check_linearization_context_matches_state( - linearization_context, state, layout.value_offsets(), - communicator + linearization_context, state, layout.value_offsets(), communicator ); - const double base_density_norm = global_vector_norm( - linearization_context.GetDensity(), communicator - ); - const double base_gravity_gradient_norm = global_vector_norm( - linearization_context.GetGravityGradient(), communicator - ); + const double base_density_norm = global_vector_norm(linearization_context.GetDensity(), communicator); + const double base_gravity_gradient_norm = + global_vector_norm(linearization_context.GetGravityGradient(), communicator); INFO("Base density norm = " << base_density_norm); INFO("Base gravity-gradient norm = " << base_gravity_gradient_norm); @@ -3409,9 +2743,7 @@ TEST_CASE( REQUIRE(base_gravity_gradient_norm > nonzero_tolerance); mfem::Operator &gradient = gravity_operator.GetGradient(state); - REQUIRE( - &gradient == static_cast(&gravity_jacobian) - ); + REQUIRE(&gradient == static_cast(&gravity_jacobian)); mfem::Vector jacobian_action; gradient.Mult(direction, jacobian_action); @@ -3445,27 +2777,19 @@ TEST_CASE( finite_difference -= minus_residual; finite_difference /= 2.0 * difference_step; - const mfem::Vector gradient_action = get_residual_block( - jacobian_action, layout, gravity_gradient_residual_block - ); - const mfem::Vector poisson_action = get_residual_block( - jacobian_action, layout, gravity_poisson_residual_block - ); - const mfem::Vector finite_difference_gradient = get_residual_block( - finite_difference, layout, gravity_gradient_residual_block - ); - const mfem::Vector finite_difference_poisson = get_residual_block( - finite_difference, layout, gravity_poisson_residual_block - ); + const mfem::Vector gradient_action = + get_residual_block(jacobian_action, layout, gravity_gradient_residual_block); + const mfem::Vector poisson_action = + get_residual_block(jacobian_action, layout, gravity_poisson_residual_block); + const mfem::Vector finite_difference_gradient = + get_residual_block(finite_difference, layout, gravity_gradient_residual_block); + const mfem::Vector finite_difference_poisson = + get_residual_block(finite_difference, layout, gravity_poisson_residual_block); - const double jacobian_gradient_norm = - global_vector_norm(gradient_action, communicator); - const double jacobian_poisson_norm = - global_vector_norm(poisson_action, communicator); - const double finite_difference_gradient_norm = - global_vector_norm(finite_difference_gradient, communicator); - const double finite_difference_poisson_norm = - global_vector_norm(finite_difference_poisson, communicator); + const double jacobian_gradient_norm = global_vector_norm(gradient_action, communicator); + const double jacobian_poisson_norm = global_vector_norm(poisson_action, communicator); + const double finite_difference_gradient_norm = global_vector_norm(finite_difference_gradient, communicator); + const double finite_difference_poisson_norm = global_vector_norm(finite_difference_poisson, communicator); REQUIRE(jacobian_gradient_norm > nonzero_tolerance); REQUIRE(jacobian_poisson_norm > nonzero_tolerance); @@ -3481,43 +2805,23 @@ TEST_CASE( mfem::Vector combined_difference(jacobian_action); combined_difference -= finite_difference; - const double absolute_gradient_error = - global_vector_norm(gradient_difference, communicator); - const double absolute_poisson_error = - global_vector_norm(poisson_difference, communicator); - const double absolute_combined_error = - global_vector_norm(combined_difference, communicator); + const double absolute_gradient_error = global_vector_norm(gradient_difference, communicator); + const double absolute_poisson_error = global_vector_norm(poisson_difference, communicator); + const double absolute_combined_error = global_vector_norm(combined_difference, communicator); - const double gradient_error = global_relative_error( - gradient_action, finite_difference_gradient, communicator - ); - const double poisson_error = global_relative_error( - poisson_action, finite_difference_poisson, communicator - ); - const double combined_error = global_relative_error( - jacobian_action, finite_difference, communicator - ); + const double gradient_error = + global_relative_error(gradient_action, finite_difference_gradient, communicator); + const double poisson_error = global_relative_error(poisson_action, finite_difference_poisson, communicator); + const double combined_error = global_relative_error(jacobian_action, finite_difference, communicator); INFO("Geometry = " << (deformed ? "deformed" : "identity")); INFO("Jacobian gradient action norm = " << jacobian_gradient_norm); - INFO( - "Finite-difference gradient norm = " - << finite_difference_gradient_norm - ); + INFO("Finite-difference gradient norm = " << finite_difference_gradient_norm); INFO("Jacobian Poisson action norm = " << jacobian_poisson_norm); - INFO( - "Finite-difference Poisson norm = " - << finite_difference_poisson_norm - ); - INFO( - "Absolute gradient residual error = " << absolute_gradient_error - ); - INFO( - "Absolute Poisson residual error = " << absolute_poisson_error - ); - INFO( - "Absolute combined residual error = " << absolute_combined_error - ); + INFO("Finite-difference Poisson norm = " << finite_difference_poisson_norm); + INFO("Absolute gradient residual error = " << absolute_gradient_error); + INFO("Absolute Poisson residual error = " << absolute_poisson_error); + INFO("Absolute combined residual error = " << absolute_combined_error); INFO("Gradient residual relative error = " << gradient_error); INFO("Poisson residual relative error = " << poisson_error); INFO("Combined residual relative error = " << combined_error); @@ -3534,8 +2838,7 @@ TEST_CASE( } TEST_CASE( "Gravity Field Jacobian Displacement Difference Converges At Second Order", - tags::integration &tags::solver &tags::gravity &tags::mfem_operators - &tags::convergence + tags::integration &tags::solver &tags::gravity &tags::mfem_operators &tags::convergence ) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); @@ -3545,30 +2848,23 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const gravity_layout layout = make_gravity_jacobian_layout(f); - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); - const mfem::Vector direction = - make_displacement_direction(layout, fields.displacement_direction_1); + const gravity_layout layout = make_gravity_jacobian_layout(f); + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); + const mfem::Vector direction = make_displacement_direction(layout, fields.displacement_direction_1); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); operators::context::gravity_field::GravityFieldRevisions revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; MPI_Comm communicator = f.mesh->GetComm(); @@ -3577,9 +2873,7 @@ TEST_CASE( REQUIRE(linearization_context.IsPrepared()); - check_linearization_context_matches_state( - linearization_context, state, layout.value_offsets(), communicator - ); + check_linearization_context_matches_state(linearization_context, state, layout.value_offsets(), communicator); mfem::Operator &gradient = gravity_operator.GetGradient(state); REQUIRE(&gradient == static_cast(&gravity_jacobian)); @@ -3589,40 +2883,38 @@ TEST_CASE( REQUIRE(jacobian_action.Size() == layout.residual_offsets().Last()); - const double jacobian_action_norm = - global_vector_norm(jacobian_action, communicator); + const double jacobian_action_norm = global_vector_norm(jacobian_action, communicator); INFO("Jacobian action norm = " << jacobian_action_norm); REQUIRE(jacobian_action_norm > 1.0e-12); - auto evaluate_geometry_centered_difference = - [&](const double difference_step) { - mfem::Vector plus_state(state); - mfem::Vector minus_state(state); - plus_state.Add(difference_step, direction); - minus_state.Add(-difference_step, direction); + auto evaluate_geometry_centered_difference = [&](const double difference_step) { + mfem::Vector plus_state(state); + mfem::Vector minus_state(state); + plus_state.Add(difference_step, direction); + minus_state.Add(-difference_step, direction); - revisions.displacement.value++; + revisions.displacement.value++; - mfem::Vector plus_residual; - gravity_operator.Prepare(plus_state, revisions); - gravity_operator.Mult(plus_state, plus_residual); + mfem::Vector plus_residual; + gravity_operator.Prepare(plus_state, revisions); + gravity_operator.Mult(plus_state, plus_residual); - revisions.displacement.value++; + revisions.displacement.value++; - mfem::Vector minus_residual; - gravity_operator.Prepare(minus_state, revisions); - gravity_operator.Mult(minus_state, minus_residual); + mfem::Vector minus_residual; + gravity_operator.Prepare(minus_state, revisions); + gravity_operator.Mult(minus_state, minus_residual); - REQUIRE(plus_residual.Size() == layout.residual_offsets().Last()); - REQUIRE(minus_residual.Size() == layout.residual_offsets().Last()); + REQUIRE(plus_residual.Size() == layout.residual_offsets().Last()); + REQUIRE(minus_residual.Size() == layout.residual_offsets().Last()); - mfem::Vector finite_difference(plus_residual); - finite_difference -= minus_residual; - finite_difference /= 2.0 * difference_step; + mfem::Vector finite_difference(plus_residual); + finite_difference -= minus_residual; + finite_difference /= 2.0 * difference_step; - return finite_difference; - }; + return finite_difference; + }; constexpr std::array difference_steps{8.0e-2, 4.0e-2, 2.0e-2}; @@ -3630,18 +2922,13 @@ TEST_CASE( std::array finite_difference_norms{}; for (std::size_t i = 0; i < difference_steps.size(); ++i) { - const mfem::Vector finite_difference = - evaluate_geometry_centered_difference(difference_steps[i]); + const mfem::Vector finite_difference = evaluate_geometry_centered_difference(difference_steps[i]); - finite_difference_norms[i] = - global_vector_norm(finite_difference, communicator); - errors[i] = global_relative_error( - finite_difference, jacobian_action, communicator - ); + finite_difference_norms[i] = global_vector_norm(finite_difference, communicator); + errors[i] = global_relative_error(finite_difference, jacobian_action, communicator); INFO( - "Step = " << difference_steps[i] << ", finite-difference norm = " - << finite_difference_norms[i] + "Step = " << difference_steps[i] << ", finite-difference norm = " << finite_difference_norms[i] << ", relative error = " << errors[i] ); @@ -3650,25 +2937,18 @@ TEST_CASE( REQUIRE(errors[i] > 0.0); } - const double first_observed_order = - std::log(errors[0] / errors[1]) / std::log(2.0); - const double second_observed_order = - std::log(errors[1] / errors[2]) / std::log(2.0); + const double first_observed_order = std::log(errors[0] / errors[1]) / std::log(2.0); + const double second_observed_order = std::log(errors[1] / errors[2]) / std::log(2.0); INFO( - "Difference steps = [" << difference_steps[0] << ", " - << difference_steps[1] << ", " - << difference_steps[2] << "]" + "Difference steps = [" << difference_steps[0] << ", " << difference_steps[1] << ", " << difference_steps[2] + << "]" ); INFO( - "Finite-difference norms = [" << finite_difference_norms[0] << ", " - << finite_difference_norms[1] << ", " + "Finite-difference norms = [" << finite_difference_norms[0] << ", " << finite_difference_norms[1] << ", " << finite_difference_norms[2] << "]" ); - INFO( - "Relative errors = [" << errors[0] << ", " << errors[1] << ", " - << errors[2] << "]" - ); + INFO("Relative errors = [" << errors[0] << ", " << errors[1] << ", " << errors[2] << "]"); INFO("First observed convergence order = " << first_observed_order); INFO("Second observed convergence order = " << second_observed_order); @@ -3694,33 +2974,25 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); - const gravity_layout layout = make_gravity_jacobian_layout(f); - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); + const gravity_layout layout = make_gravity_jacobian_layout(f); + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector state = make_gravity_jacobian_state(f, layout, true); - mfem::Vector direction = make_combined_fixed_geometry_direction(layout); - set_value_block( - direction, layout, displacement_block, fields.displacement_direction_2 + mfem::Vector direction = make_combined_fixed_geometry_direction(layout); + set_value_block(direction, layout, displacement_block, fields.displacement_direction_2); + + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless ); - - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); operators::context::gravity_field::GravityFieldRevisions base_revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} }; MPI_Comm communicator = f.mesh->GetComm(); @@ -3733,9 +3005,7 @@ TEST_CASE( REQUIRE(linearization_context.IsPrepared()); - check_linearization_context_matches_state( - linearization_context, state, layout.value_offsets(), communicator - ); + check_linearization_context_matches_state(linearization_context, state, layout.value_offsets(), communicator); mfem::Operator &gradient = gravity_operator.GetGradient(state); REQUIRE(&gradient == static_cast(&gravity_jacobian)); @@ -3780,29 +3050,19 @@ TEST_CASE( finite_difference -= minus_residual; finite_difference /= 2.0 * difference_step; - const mfem::Vector gradient_action = get_residual_block( - jacobian_action, layout, gravity_gradient_residual_block - ); - const mfem::Vector poisson_action = get_residual_block( - jacobian_action, layout, gravity_poisson_residual_block - ); - const mfem::Vector finite_difference_gradient = get_residual_block( - finite_difference, layout, gravity_gradient_residual_block - ); - const mfem::Vector finite_difference_poisson = get_residual_block( - finite_difference, layout, gravity_poisson_residual_block - ); + const mfem::Vector gradient_action = get_residual_block(jacobian_action, layout, gravity_gradient_residual_block); + const mfem::Vector poisson_action = get_residual_block(jacobian_action, layout, gravity_poisson_residual_block); + const mfem::Vector finite_difference_gradient = + get_residual_block(finite_difference, layout, gravity_gradient_residual_block); + const mfem::Vector finite_difference_poisson = + get_residual_block(finite_difference, layout, gravity_poisson_residual_block); - const double jacobian_gradient_norm = - global_vector_norm(gradient_action, communicator); - const double jacobian_poisson_norm = - global_vector_norm(poisson_action, communicator); - const double finite_difference_gradient_norm = - global_vector_norm(finite_difference_gradient, communicator); - const double finite_difference_poisson_norm = - global_vector_norm(finite_difference_poisson, communicator); + const double jacobian_gradient_norm = global_vector_norm(gradient_action, communicator); + const double jacobian_poisson_norm = global_vector_norm(poisson_action, communicator); + const double finite_difference_gradient_norm = global_vector_norm(finite_difference_gradient, communicator); + const double finite_difference_poisson_norm = global_vector_norm(finite_difference_poisson, communicator); - constexpr double nonzero_tolerance = 1.0e-12; + constexpr double nonzero_tolerance = 1.0e-12; REQUIRE(jacobian_gradient_norm > nonzero_tolerance); REQUIRE(jacobian_poisson_norm > nonzero_tolerance); @@ -3818,26 +3078,16 @@ TEST_CASE( mfem::Vector combined_difference(jacobian_action); combined_difference -= finite_difference; - const double absolute_gradient_error = - global_vector_norm(gradient_difference, communicator); - const double absolute_poisson_error = - global_vector_norm(poisson_difference, communicator); - const double absolute_combined_error = - global_vector_norm(combined_difference, communicator); + const double absolute_gradient_error = global_vector_norm(gradient_difference, communicator); + const double absolute_poisson_error = global_vector_norm(poisson_difference, communicator); + const double absolute_combined_error = global_vector_norm(combined_difference, communicator); - const double gradient_error = global_relative_error( - gradient_action, finite_difference_gradient, communicator - ); - const double poisson_error = global_relative_error( - poisson_action, finite_difference_poisson, communicator - ); - const double combined_error = - global_relative_error(jacobian_action, finite_difference, communicator); + const double gradient_error = global_relative_error(gradient_action, finite_difference_gradient, communicator); + const double poisson_error = global_relative_error(poisson_action, finite_difference_poisson, communicator); + const double combined_error = global_relative_error(jacobian_action, finite_difference, communicator); INFO("Jacobian gradient action norm = " << jacobian_gradient_norm); - INFO( - "Finite-difference gradient norm = " << finite_difference_gradient_norm - ); + INFO("Finite-difference gradient norm = " << finite_difference_gradient_norm); INFO("Jacobian Poisson action norm = " << jacobian_poisson_norm); INFO("Finite-difference Poisson norm = " << finite_difference_poisson_norm); INFO("Absolute gradient residual error = " << absolute_gradient_error); @@ -3866,11 +3116,10 @@ TEST_CASE( REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr); - const gravity_layout layout = make_gravity_jacobian_layout(f); - const HdivMassVariationTestFields fields = - make_hdiv_mass_variation_test_fields(f); - const mfem::Vector density = make_source_variation_density(f); - const mfem::Vector displacement = fields.displacement; + const gravity_layout layout = make_gravity_jacobian_layout(f); + const HdivMassVariationTestFields fields = make_hdiv_mass_variation_test_fields(f); + const mfem::Vector density = make_source_variation_density(f); + const mfem::Vector displacement = fields.displacement; mfem::ParGridFunction legacy_displacement(f.displacementFes.get()); legacy_displacement.SetFromTrueDofs(displacement); @@ -3881,22 +3130,20 @@ TEST_CASE( REQUIRE(f.gravityContext.BT != nullptr); REQUIRE(f.gravityContext.block_prec != nullptr); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); - operators::context::gravity_field::GravityFieldGeometryContext - reduced_geometry_context(f, *f.domainMapperStateless); - operators::ReducedGravityFieldOperator reduced_operator( - gravity_operator, reduced_geometry_context, displacement + operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context( + f, *f.domainMapperStateless ); + operators::ReducedGravityFieldOperator reduced_operator(gravity_operator, reduced_geometry_context, displacement); REQUIRE(reduced_geometry_context.IsPrepared()); REQUIRE(reduced_operator.Width() == layout.residual_offsets().Last()); @@ -3916,17 +3163,13 @@ TEST_CASE( REQUIRE(right_hand_side.Size() == reduced_operator.Height()); - MPI_Comm communicator = f.mesh->GetComm(); - const double right_hand_side_norm = - global_norm(right_hand_side, communicator); + MPI_Comm communicator = f.mesh->GetComm(); + const double right_hand_side_norm = global_norm(right_hand_side, communicator); INFO("Reduced gravity-system size = " << reduced_operator.Height()); INFO("Gravity right-hand-side norm = " << right_hand_side_norm); INFO("Mass preparations before solve = " << mass_preparations_before_solve); - INFO( - "Source preparations before solve = " - << source_preparations_before_solve - ); + INFO("Source preparations before solve = " << source_preparations_before_solve); REQUIRE(right_hand_side_norm > 0.0); @@ -3946,9 +3189,7 @@ TEST_CASE( minres.SetPrintLevel(1); MEAN_FIELD_PROFILE_RESET(); - MEAN_FIELD_PROFILE_CALL_WARMUP( - "MINRES total", 0, minres.Mult(right_hand_side, gravity_state) - ); + MEAN_FIELD_PROFILE_CALL_WARMUP("MINRES total", 0, minres.Mult(right_hand_side, gravity_state)); MEAN_FIELD_PROFILE_PRINT(communicator); REQUIRE(gravity_state.Size() == reduced_operator.Width()); @@ -3959,9 +3200,7 @@ TEST_CASE( reduced_geometry_context.GetSourceOperator().GetPreparationCount(); INFO("Mass preparations after solve = " << mass_preparations_after_solve); - INFO( - "Source preparations after solve = " << source_preparations_after_solve - ); + INFO("Source preparations after solve = " << source_preparations_after_solve); CHECK(mass_preparations_after_solve == mass_preparations_before_solve); CHECK(source_preparations_after_solve == source_preparations_before_solve); @@ -3972,34 +3211,23 @@ TEST_CASE( mfem::Vector reduced_residual(operator_action); reduced_residual -= right_hand_side; - const mfem::Vector gradient_residual = get_residual_block( - reduced_residual, layout, gravity_gradient_residual_block - ); - const mfem::Vector poisson_residual = get_residual_block( - reduced_residual, layout, gravity_poisson_residual_block - ); - const mfem::Vector solved_gravity_gradient = get_residual_block( - gravity_state, layout, gravity_gradient_residual_block - ); - const mfem::Vector solved_gravity_potential = get_residual_block( - gravity_state, layout, gravity_poisson_residual_block - ); + const mfem::Vector gradient_residual = + get_residual_block(reduced_residual, layout, gravity_gradient_residual_block); + const mfem::Vector poisson_residual = get_residual_block(reduced_residual, layout, gravity_poisson_residual_block); + const mfem::Vector solved_gravity_gradient = + get_residual_block(gravity_state, layout, gravity_gradient_residual_block); + const mfem::Vector solved_gravity_potential = + get_residual_block(gravity_state, layout, gravity_poisson_residual_block); - const double gravity_state_norm = global_norm(gravity_state, communicator); - const double gravity_gradient_norm = - global_norm(solved_gravity_gradient, communicator); - const double gravity_potential_norm = - global_norm(solved_gravity_potential, communicator); - const double residual_norm = global_norm(reduced_residual, communicator); - const double gradient_residual_norm = - global_norm(gradient_residual, communicator); - const double poisson_residual_norm = - global_norm(poisson_residual, communicator); - const double relative_residual = residual_norm / right_hand_side_norm; - const double relative_gradient_residual = - gradient_residual_norm / right_hand_side_norm; - const double relative_poisson_residual = - poisson_residual_norm / right_hand_side_norm; + const double gravity_state_norm = global_norm(gravity_state, communicator); + const double gravity_gradient_norm = global_norm(solved_gravity_gradient, communicator); + const double gravity_potential_norm = global_norm(solved_gravity_potential, communicator); + const double residual_norm = global_norm(reduced_residual, communicator); + const double gradient_residual_norm = global_norm(gradient_residual, communicator); + const double poisson_residual_norm = global_norm(poisson_residual, communicator); + const double relative_residual = residual_norm / right_hand_side_norm; + const double relative_gradient_residual = gradient_residual_norm / right_hand_side_norm; + const double relative_poisson_residual = poisson_residual_norm / right_hand_side_norm; INFO("MINRES converged = " << minres.GetConverged()); INFO("MINRES iterations = " << minres.GetNumIterations()); @@ -4009,9 +3237,7 @@ TEST_CASE( INFO("Solved gravity-potential norm = " << gravity_potential_norm); INFO("Direct residual norm = " << residual_norm); INFO("Direct relative residual = " << relative_residual); - INFO( - "Relative gradient-equation residual = " << relative_gradient_residual - ); + INFO("Relative gradient-equation residual = " << relative_gradient_residual); INFO("Relative Poisson-equation residual = " << relative_poisson_residual); CHECK(minres.GetConverged()); @@ -4023,41 +3249,26 @@ TEST_CASE( constexpr double direct_residual_tolerance = 1.0e-11; CHECK_THAT(relative_residual, WithinAbs(0.0, direct_residual_tolerance)); - CHECK_THAT( - relative_gradient_residual, WithinAbs(0.0, direct_residual_tolerance) - ); - CHECK_THAT( - relative_poisson_residual, WithinAbs(0.0, direct_residual_tolerance) - ); + CHECK_THAT(relative_gradient_residual, WithinAbs(0.0, direct_residual_tolerance)); + CHECK_THAT(relative_poisson_residual, WithinAbs(0.0, direct_residual_tolerance)); mfem::Vector full_state(layout.value_offsets().Last()); full_state = 0.0; set_value_block(full_state, layout, density_block, density); set_value_block(full_state, layout, displacement_block, displacement); - set_value_block( - full_state, layout, gravity_gradient_block, solved_gravity_gradient - ); - set_value_block( - full_state, layout, gravity_potential_block, solved_gravity_potential - ); + set_value_block(full_state, layout, gravity_gradient_block, solved_gravity_gradient); + set_value_block(full_state, layout, gravity_potential_block, solved_gravity_potential); - const operators::context::gravity_field::GravityFieldRevisions - full_state_revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + const operators::context::gravity_field::GravityFieldRevisions full_state_revisions{ + .discretization = {1}, .displacement = {1}, .density = {1}, .gravity_gradient = {1}, .gravity_potential = {1} + }; gravity_operator.Prepare(full_state, full_state_revisions); REQUIRE(linearization_context.IsPrepared()); - check_linearization_context_matches_state( - linearization_context, full_state, layout.value_offsets(), communicator - ); + check_linearization_context_matches_state(linearization_context, full_state, layout.value_offsets(), communicator); mfem::Vector full_residual; gravity_operator.Mult(full_state, full_residual); @@ -4068,27 +3279,19 @@ TEST_CASE( residual_representation_difference -= reduced_residual; const double residual_representation_error = - global_norm(residual_representation_difference, communicator) / - right_hand_side_norm; - const double full_relative_residual = - global_norm(full_residual, communicator) / right_hand_side_norm; + global_norm(residual_representation_difference, communicator) / right_hand_side_norm; + const double full_relative_residual = global_norm(full_residual, communicator) / right_hand_side_norm; INFO("Full gravity residual relative norm = " << full_relative_residual); - INFO( - "Reduced/full residual representation error = " - << residual_representation_error - ); + INFO("Reduced/full residual representation error = " << residual_representation_error); - CHECK_THAT( - full_relative_residual, WithinAbs(0.0, direct_residual_tolerance) - ); + CHECK_THAT(full_relative_residual, WithinAbs(0.0, direct_residual_tolerance)); CHECK_THAT(residual_representation_error, WithinAbs(0.0, 1.0e-12)); } TEST_CASE( "Gravity Hdiv Operator Difference Is Localized To Vacuum Compactification", - tags::integration &tags::solver &tags::gravity &tags::mfem_operators - &tags::legacy_comparison + tags::integration &tags::solver &tags::gravity &tags::mfem_operators &tags::legacy_comparison ) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); @@ -4100,23 +3303,17 @@ TEST_CASE( using form = blocks::gravity_field_form; constexpr auto displacement_block = - mean_field::utils::blocks::get_value_block( - blocks::displacement_field.geometry_term - ); + mean_field::utils::blocks::get_value_block(blocks::displacement_field.geometry_term); constexpr auto gravity_gradient_block = - mean_field::utils::blocks::get_value_block( - blocks::gravity_field.gradient_term - ); + mean_field::utils::blocks::get_value_block(blocks::gravity_field.gradient_term); constexpr auto gravity_gradient_residual_block = - mean_field::utils::blocks::get_residual_block( - blocks::gravity_field.gradient_term - ); + mean_field::utils::blocks::get_residual_block(blocks::gravity_field.gradient_term); const std::array value_sizes{ - f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), - f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize() }; const std::array residual_sizes{ @@ -4125,17 +3322,15 @@ TEST_CASE( const blocks::form_layout layout(value_sizes, residual_sizes); - const mfem::Vector gravity_gradient_true = - make_full_support_gravity_gradient(f); + const mfem::Vector gravity_gradient_true = make_full_support_gravity_gradient(f); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); REQUIRE(global_vector_norm(gravity_gradient_true, communicator) > 0.0); for (const bool deformed : std::array{false, true}) { DYNAMIC_SECTION("Geometry = " << (deformed ? "deformed" : "identity")) { - const mfem::Vector displacement_true = - make_stateless_reference_displacement(f, deformed); + const mfem::Vector displacement_true = make_stateless_reference_displacement(f, deformed); /* * Prepare the legacy geometry and assemble its PA operator. @@ -4150,78 +3345,62 @@ TEST_CASE( REQUIRE(f.gravityContext.m_form != nullptr); - operators::context::gravity_field::GravityFieldLinearizationContext - linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator gravity_jacobian( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), layout.residual_offsets() + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets() ); operators::GravityFieldOperator gravity_operator( - f, *f.domainMapperStateless, linearization_context, - layout.value_offsets(), gravity_jacobian + f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian ); mfem::Vector state(layout.value_offsets().Last()); state = 0.0; for (int i = 0; i < displacement_true.Size(); ++i) { - state(layout.offset(displacement_block) + i) = - displacement_true(i); + state(layout.offset(displacement_block) + i) = displacement_true(i); } for (int i = 0; i < gravity_gradient_true.Size(); ++i) { - state(layout.offset(gravity_gradient_block) + i) = - gravity_gradient_true(i); + state(layout.offset(gravity_gradient_block) + i) = gravity_gradient_true(i); } - const operators::context::gravity_field::GravityFieldRevisions - revisions{ - .discretization = {1}, - .displacement = {1}, - .density = {1}, - .gravity_gradient = {1}, - .gravity_potential = {1} - }; + const operators::context::gravity_field::GravityFieldRevisions revisions{ + .discretization = {1}, + .displacement = {1}, + .density = {1}, + .gravity_gradient = {1}, + .gravity_potential = {1} + }; gravity_operator.Prepare(state, revisions); mfem::Vector new_residual; gravity_operator.Mult(state, new_residual); - mfem::Vector new_operator_mass_action( - layout.size(gravity_gradient_residual_block) - ); + mfem::Vector new_operator_mass_action(layout.size(gravity_gradient_residual_block)); for (int i = 0; i < new_operator_mass_action.Size(); ++i) { - new_operator_mass_action(i) = new_residual( - layout.offset(gravity_gradient_residual_block) + i - ); + new_operator_mass_action(i) = new_residual(layout.offset(gravity_gradient_residual_block) + i); } - mfem::Vector legacy_operator_mass_action( - f.gravityFluxFes->GetTrueVSize() - ); + mfem::Vector legacy_operator_mass_action(f.gravityFluxFes->GetTrueVSize()); legacy_operator_mass_action = 0.0; - f.gravityContext.m_form->Mult( - gravity_gradient_true, legacy_operator_mass_action - ); + f.gravityContext.m_form->Mult(gravity_gradient_true, legacy_operator_mass_action); const StatelessHDivMassReference new_reference = - evaluate_stateless_hdiv_mass_quadrature_reference( - f, gravity_gradient_true, displacement_true - ); + evaluate_stateless_hdiv_mass_quadrature_reference(f, gravity_gradient_true, displacement_true); const StatelessHDivMassReference legacy_reference = assemble_legacy_hdiv_mass_reference(f, gravity_gradient_true); mfem::Vector new_decomposed_action(new_reference.stellar_action); new_decomposed_action += new_reference.vacuum_action; - mfem::Vector legacy_decomposed_action( - legacy_reference.stellar_action - ); + mfem::Vector legacy_decomposed_action(legacy_reference.stellar_action); legacy_decomposed_action += legacy_reference.vacuum_action; mfem::Vector total_gap(new_reference.total_action); @@ -4240,157 +3419,84 @@ TEST_CASE( unexplained_by_vacuum -= vacuum_gap; const double new_operator_reference_error = - global_relative_difference( - new_operator_mass_action, new_reference.total_action, - communicator - ); + global_relative_difference(new_operator_mass_action, new_reference.total_action, communicator); const double legacy_operator_reference_error = - global_relative_difference( - legacy_operator_mass_action, legacy_reference.total_action, - communicator - ); + global_relative_difference(legacy_operator_mass_action, legacy_reference.total_action, communicator); - const double new_decomposition_error = global_relative_difference( - new_reference.total_action, new_decomposed_action, communicator - ); + const double new_decomposition_error = + global_relative_difference(new_reference.total_action, new_decomposed_action, communicator); const double legacy_decomposition_error = - global_relative_difference( - legacy_reference.total_action, legacy_decomposed_action, - communicator - ); + global_relative_difference(legacy_reference.total_action, legacy_decomposed_action, communicator); - const double gap_decomposition_error = global_relative_difference( - total_gap, decomposed_gap, communicator - ); + const double gap_decomposition_error = global_relative_difference(total_gap, decomposed_gap, communicator); const double stellar_relative_difference = - global_relative_difference( - new_reference.stellar_action, - legacy_reference.stellar_action, communicator - ); + global_relative_difference(new_reference.stellar_action, legacy_reference.stellar_action, communicator); const double vacuum_relative_difference = - global_relative_difference( - new_reference.vacuum_action, legacy_reference.vacuum_action, - communicator - ); + global_relative_difference(new_reference.vacuum_action, legacy_reference.vacuum_action, communicator); - const double total_relative_difference = global_relative_difference( - new_reference.total_action, legacy_reference.total_action, - communicator - ); + const double total_relative_difference = + global_relative_difference(new_reference.total_action, legacy_reference.total_action, communicator); - const double total_gap_norm = - global_vector_norm(total_gap, communicator); + const double total_gap_norm = global_vector_norm(total_gap, communicator); - const double stellar_gap_norm = - global_vector_norm(stellar_gap, communicator); + const double stellar_gap_norm = global_vector_norm(stellar_gap, communicator); - const double vacuum_gap_norm = - global_vector_norm(vacuum_gap, communicator); + const double vacuum_gap_norm = global_vector_norm(vacuum_gap, communicator); - const double unexplained_gap_norm = - global_vector_norm(unexplained_by_vacuum, communicator); + const double unexplained_gap_norm = global_vector_norm(unexplained_by_vacuum, communicator); const double stellar_fraction_of_gap = - stellar_gap_norm / - std::max( - total_gap_norm, std::numeric_limits::epsilon() - ); + stellar_gap_norm / std::max(total_gap_norm, std::numeric_limits::epsilon()); const double unexplained_fraction_of_gap = - unexplained_gap_norm / - std::max( - total_gap_norm, std::numeric_limits::epsilon() - ); + unexplained_gap_norm / std::max(total_gap_norm, std::numeric_limits::epsilon()); - const double new_total_energy = global_vector_dot( - gravity_gradient_true, new_reference.total_action, communicator - ); + const double new_total_energy = + global_vector_dot(gravity_gradient_true, new_reference.total_action, communicator); - const double legacy_total_energy = global_vector_dot( - gravity_gradient_true, legacy_reference.total_action, - communicator - ); + const double legacy_total_energy = + global_vector_dot(gravity_gradient_true, legacy_reference.total_action, communicator); - const double new_stellar_energy = global_vector_dot( - gravity_gradient_true, new_reference.stellar_action, - communicator - ); + const double new_stellar_energy = + global_vector_dot(gravity_gradient_true, new_reference.stellar_action, communicator); - const double legacy_stellar_energy = global_vector_dot( - gravity_gradient_true, legacy_reference.stellar_action, - communicator - ); + const double legacy_stellar_energy = + global_vector_dot(gravity_gradient_true, legacy_reference.stellar_action, communicator); - const double new_vacuum_energy = global_vector_dot( - gravity_gradient_true, new_reference.vacuum_action, communicator - ); + const double new_vacuum_energy = + global_vector_dot(gravity_gradient_true, new_reference.vacuum_action, communicator); - const double legacy_vacuum_energy = global_vector_dot( - gravity_gradient_true, legacy_reference.vacuum_action, - communicator - ); + const double legacy_vacuum_energy = + global_vector_dot(gravity_gradient_true, legacy_reference.vacuum_action, communicator); - const double total_energy_gap = - new_total_energy - legacy_total_energy; + const double total_energy_gap = new_total_energy - legacy_total_energy; - const double stellar_energy_gap = - new_stellar_energy - legacy_stellar_energy; + const double stellar_energy_gap = new_stellar_energy - legacy_stellar_energy; - const double vacuum_energy_gap = - new_vacuum_energy - legacy_vacuum_energy; + const double vacuum_energy_gap = new_vacuum_energy - legacy_vacuum_energy; const double energy_gap_decomposition_error = - std::abs( - total_energy_gap - stellar_energy_gap - vacuum_energy_gap - ) / - std::max( - std::abs(total_energy_gap), - std::numeric_limits::epsilon() - ); + std::abs(total_energy_gap - stellar_energy_gap - vacuum_energy_gap) / + std::max(std::abs(total_energy_gap), std::numeric_limits::epsilon()); INFO("Geometry = " << (deformed ? "deformed" : "identity")); - INFO( - "New operator/reference error = " - << new_operator_reference_error - ); - INFO( - "Legacy operator/reference error = " - << legacy_operator_reference_error - ); - INFO( - "New regional decomposition error = " << new_decomposition_error - ); - INFO( - "Legacy regional decomposition error = " - << legacy_decomposition_error - ); + INFO("New operator/reference error = " << new_operator_reference_error); + INFO("Legacy operator/reference error = " << legacy_operator_reference_error); + INFO("New regional decomposition error = " << new_decomposition_error); + INFO("Legacy regional decomposition error = " << legacy_decomposition_error); INFO("Gap decomposition error = " << gap_decomposition_error); - INFO( - "New/legacy total mass-action difference = " - << total_relative_difference - ); - INFO( - "New/legacy stellar mass-action difference = " - << stellar_relative_difference - ); - INFO( - "New/legacy vacuum mass-action difference = " - << vacuum_relative_difference - ); + INFO("New/legacy total mass-action difference = " << total_relative_difference); + INFO("New/legacy stellar mass-action difference = " << stellar_relative_difference); + INFO("New/legacy vacuum mass-action difference = " << vacuum_relative_difference); INFO("Total operator-gap norm = " << total_gap_norm); INFO("Stellar operator-gap norm = " << stellar_gap_norm); INFO("Vacuum operator-gap norm = " << vacuum_gap_norm); - INFO( - "Stellar fraction of operator gap = " << stellar_fraction_of_gap - ); - INFO( - "Operator gap unexplained by vacuum = " - << unexplained_fraction_of_gap - ); + INFO("Stellar fraction of operator gap = " << stellar_fraction_of_gap); + INFO("Operator gap unexplained by vacuum = " << unexplained_fraction_of_gap); INFO("New total H(div) energy = " << new_total_energy); INFO("Legacy total H(div) energy = " << legacy_total_energy); INFO("New stellar H(div) energy = " << new_stellar_energy); @@ -4400,10 +3506,7 @@ TEST_CASE( INFO("Total H(div) energy gap = " << total_energy_gap); INFO("Stellar H(div) energy gap = " << stellar_energy_gap); INFO("Vacuum H(div) energy gap = " << vacuum_energy_gap); - INFO( - "Energy-gap decomposition error = " - << energy_gap_decomposition_error - ); + INFO("Energy-gap decomposition error = " << energy_gap_decomposition_error); REQUIRE(new_reference.stellar_elements > 0); REQUIRE(new_reference.vacuum_elements > 0); @@ -4422,35 +3525,17 @@ TEST_CASE( constexpr double stellar_parity_tolerance = 1.0e-10; constexpr double gap_localization_tolerance = 1.0e-9; - CHECK_THAT( - new_operator_reference_error, - Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance) - ); + CHECK_THAT(new_operator_reference_error, Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance)); - CHECK_THAT( - legacy_operator_reference_error, - Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance) - ); + CHECK_THAT(legacy_operator_reference_error, Catch::Matchers::WithinAbs(0.0, operator_reference_tolerance)); - CHECK_THAT( - new_decomposition_error, - Catch::Matchers::WithinAbs(0.0, decomposition_tolerance) - ); + CHECK_THAT(new_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance)); - CHECK_THAT( - legacy_decomposition_error, - Catch::Matchers::WithinAbs(0.0, decomposition_tolerance) - ); + CHECK_THAT(legacy_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance)); - CHECK_THAT( - gap_decomposition_error, - Catch::Matchers::WithinAbs(0.0, decomposition_tolerance) - ); + CHECK_THAT(gap_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance)); - CHECK_THAT( - stellar_relative_difference, - Catch::Matchers::WithinAbs(0.0, stellar_parity_tolerance) - ); + CHECK_THAT(stellar_relative_difference, Catch::Matchers::WithinAbs(0.0, stellar_parity_tolerance)); /* * The previous global comparison already showed a material @@ -4464,20 +3549,11 @@ TEST_CASE( * If these pass, the old/new difference has been directly * localized to the vacuum compactification prescription. */ - CHECK_THAT( - stellar_fraction_of_gap, - Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance) - ); + CHECK_THAT(stellar_fraction_of_gap, Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance)); - CHECK_THAT( - unexplained_fraction_of_gap, - Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance) - ); + CHECK_THAT(unexplained_fraction_of_gap, Catch::Matchers::WithinAbs(0.0, gap_localization_tolerance)); - CHECK_THAT( - energy_gap_decomposition_error, - Catch::Matchers::WithinAbs(0.0, decomposition_tolerance) - ); + CHECK_THAT(energy_gap_decomposition_error, Catch::Matchers::WithinAbs(0.0, decomposition_tolerance)); } } } diff --git a/tests/operators/kernels/barotropic_closure_kernels.cpp b/tests/operators/kernels/barotropic_closure_kernels.cpp index 53dd05a..39365b4 100644 --- a/tests/operators/kernels/barotropic_closure_kernels.cpp +++ b/tests/operators/kernels/barotropic_closure_kernels.cpp @@ -44,23 +44,17 @@ namespace { } mfem::Vector make_base_density(const mean_field::fem::FEM &f) { - mfem::FunctionCoefficient coefficient( - [](const mfem::Vector &position) { - return 0.55 + 0.025 * position(0) - 0.010 * position(1) + - 0.006 * position(2); - } - ); + mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { + return 0.55 + 0.025 * position(0) - 0.010 * position(1) + 0.006 * position(2); + }); return project_scalar_field(*f.densityFes, coefficient); } mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) { - mfem::FunctionCoefficient coefficient( - [](const mfem::Vector &position) { - return 0.90 + 0.020 * position(0) - 0.010 * position(1) + - 0.005 * position(2); - } - ); + mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { + return 0.90 + 0.020 * position(0) - 0.010 * position(1) + 0.005 * position(2); + }); return project_scalar_field(*f.enthalpyFes, coefficient); } @@ -69,23 +63,20 @@ namespace { TEST_CASE( "Barotropic Closure Vanishes For A Representable Constant State", - tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels - &tags::barotrope + tags::hydro &tags::residuals &tags::unit &tags::closure &tags::kernels &tags::barotrope ) { - auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - constexpr double enthalpyValue = 0.8; + constexpr double enthalpyValue = 0.8; - const double densityValue = barotrope.density_from_enthalpy(enthalpyValue); + const double densityValue = barotrope.density_from_enthalpy(enthalpyValue); - const mfem::Vector enthalpy = - project_constant(*f.enthalpyFes, enthalpyValue); + const mfem::Vector enthalpy = project_constant(*f.enthalpyFes, enthalpyValue); - const mfem::Vector density = project_constant(*f.densityFes, densityValue); + const mfem::Vector density = project_constant(*f.densityFes, densityValue); const mfem::Vector displacement = make_zero_displacement(f); @@ -93,19 +84,17 @@ TEST_CASE( mfem::Vector scale; mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement, - residual + f, *f.domainMapperStateless, barotrope, density, enthalpy, displacement, residual ); mean_field::operators::kernels::apply_barotropic_closure_density_action( f, *f.domainMapperStateless, barotrope, density, displacement, scale ); - const MPI_Comm communicator = f.mesh->GetComm(); + const MPI_Comm communicator = f.mesh->GetComm(); - const double relativeResidual = - gravity_prepared_test_utils::global_norm(residual, communicator) / - gravity_prepared_test_utils::global_norm(scale, communicator); + const double relativeResidual = gravity_prepared_test_utils::global_norm(residual, communicator) / + gravity_prepared_test_utils::global_norm(scale, communicator); INFO("Relative constant-state closure residual = " << relativeResidual); @@ -114,40 +103,34 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Density Action Matches The Stellar Mass Matrix", - tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels - &tags::barotrope + tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels &tags::barotrope ) { - auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); const mfem::Vector displacement = make_zero_displacement(f); const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.37 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.37); mfem::Vector kernelAction; mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, displacement, - kernelAction + f, *f.domainMapperStateless, barotrope, densityVariation, displacement, kernelAction ); + using Schema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + using Stellar = mean_field::utils::domain::Stellar; + mfem::Array stellarMarker(f.mesh->attributes.Max()); stellarMarker = 0; - const int vacuumAttribute = - f.domainMapperStateless->GetVacuumElementAttribute(); - - for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); - ++attributeIndex) { + for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) { const int attribute = f.mesh->attributes[attributeIndex]; - if (attribute != vacuumAttribute) { + if (Schema::template attribute_belongs_to(attribute)) { stellarMarker[attribute - 1] = 1; } } @@ -159,9 +142,7 @@ TEST_CASE( massForm.Assemble(); massForm.Finalize(); - std::unique_ptr massMatrix( - massForm.ParallelAssemble() - ); + std::unique_ptr massMatrix(massForm.ParallelAssemble()); REQUIRE(massMatrix != nullptr); REQUIRE(massMatrix->Width() == densityVariation.Size()); @@ -170,9 +151,8 @@ TEST_CASE( referenceAction = 0.0; massMatrix->Mult(densityVariation, referenceAction); - const double relativeError = gravity_prepared_test_utils::relative_error( - kernelAction, referenceAction, f.mesh->GetComm() - ); + const double relativeError = + gravity_prepared_test_utils::relative_error(kernelAction, referenceAction, f.mesh->GetComm()); INFO("Density-action mass-matrix error = " << relativeError); @@ -181,32 +161,24 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Jacobian Matches A Combined Centered Difference", - tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels - &tags::barotrope + tags::hydro &tags::jacobian &tags::unit &tags::closure &tags::kernels &tags::barotrope ) { - auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - mfem::FunctionCoefficient densityCoefficient( - [](const mfem::Vector &position) { - return 0.4 + 0.03 * position(0) - 0.01 * position(1); - } - ); + mfem::FunctionCoefficient densityCoefficient([](const mfem::Vector &position) { + return 0.4 + 0.03 * position(0) - 0.01 * position(1); + }); - mfem::FunctionCoefficient enthalpyCoefficient( - [](const mfem::Vector &position) { - return 0.9 + 0.02 * position(0) - 0.01 * position(1); - } - ); + mfem::FunctionCoefficient enthalpyCoefficient([](const mfem::Vector &position) { + return 0.9 + 0.02 * position(0) - 0.01 * position(1); + }); - mfem::FunctionCoefficient enthalpyVariationCoefficient( - [](const mfem::Vector &position) { - return 0.07 + 0.015 * position(0) + 0.008 * position(2); - } - ); + mfem::FunctionCoefficient enthalpyVariationCoefficient([](const mfem::Vector &position) { + return 0.07 + 0.015 * position(0) + 0.008 * position(2); + }); mfem::ParGridFunction densityField(f.densityFes.get()); mfem::ParGridFunction enthalpyField(f.enthalpyFes.get()); @@ -225,46 +197,33 @@ TEST_CASE( enthalpyVariationField.GetTrueDofs(enthalpyVariation); const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.63 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.63); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0); constexpr double differenceStep = 1.0e-6; const mfem::Vector plusDensity = - gravity_prepared_test_utils::linear_combination( - density, 1.0, densityVariation, differenceStep - ); + gravity_prepared_test_utils::linear_combination(density, 1.0, densityVariation, differenceStep); const mfem::Vector minusDensity = - gravity_prepared_test_utils::linear_combination( - density, 1.0, densityVariation, -differenceStep - ); + gravity_prepared_test_utils::linear_combination(density, 1.0, densityVariation, -differenceStep); const mfem::Vector plusEnthalpy = - gravity_prepared_test_utils::linear_combination( - enthalpy, 1.0, enthalpyVariation, differenceStep - ); + gravity_prepared_test_utils::linear_combination(enthalpy, 1.0, enthalpyVariation, differenceStep); const mfem::Vector minusEnthalpy = - gravity_prepared_test_utils::linear_combination( - enthalpy, 1.0, enthalpyVariation, -differenceStep - ); + gravity_prepared_test_utils::linear_combination(enthalpy, 1.0, enthalpyVariation, -differenceStep); mfem::Vector plusResidual; mfem::Vector minusResidual; mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy, - displacement, plusResidual + f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy, displacement, plusResidual ); mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy, - displacement, minusResidual + f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy, displacement, minusResidual ); mfem::Vector finiteDifference(plusResidual); @@ -275,21 +234,18 @@ TEST_CASE( mfem::Vector enthalpyAction; mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, displacement, - densityAction + f, *f.domainMapperStateless, barotrope, densityVariation, displacement, densityAction ); mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action( - f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation, - displacement, enthalpyAction + f, *f.domainMapperStateless, barotrope, enthalpy, enthalpyVariation, displacement, enthalpyAction ); mfem::Vector analyticAction(densityAction); analyticAction += enthalpyAction; - const double relativeError = gravity_prepared_test_utils::relative_error( - analyticAction, finiteDifference, f.mesh->GetComm() - ); + const double relativeError = + gravity_prepared_test_utils::relative_error(analyticAction, finiteDifference, f.mesh->GetComm()); INFO("Combined EOS Jacobian error = " << relativeError); @@ -298,57 +254,45 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Density Action Excludes Vacuum And Uses Mapped Volume", - tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope - &tags::kernels + tags::hydro &tags::mapping &tags::unit &tags::closure &tags::barotrope &tags::kernels ) { - auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + auto args = test_utils::setup_args(); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - const mfem::Vector stellarDensity = - gravity_prepared_test_utils::make_domain_supported_density(f, true); + const mfem::Vector stellarDensity = gravity_prepared_test_utils::make_domain_supported_density(f, true); - const mfem::Vector vacuumDensity = - gravity_prepared_test_utils::make_domain_supported_density(f, false); + const mfem::Vector vacuumDensity = gravity_prepared_test_utils::make_domain_supported_density(f, false); - const mfem::Vector identityDisplacement = - gravity_prepared_test_utils::make_displacement(f, 0.0); + const mfem::Vector identityDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.0); - const mfem::Vector deformedDisplacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector deformedDisplacement = gravity_prepared_test_utils::make_displacement(f, 1.0); mfem::Vector stellarAction; mfem::Vector vacuumAction; mfem::Vector deformedAction; mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, stellarDensity, - identityDisplacement, stellarAction + f, *f.domainMapperStateless, barotrope, stellarDensity, identityDisplacement, stellarAction ); mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, vacuumDensity, - identityDisplacement, vacuumAction + f, *f.domainMapperStateless, barotrope, vacuumDensity, identityDisplacement, vacuumAction ); mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, stellarDensity, - deformedDisplacement, deformedAction + f, *f.domainMapperStateless, barotrope, stellarDensity, deformedDisplacement, deformedAction ); const MPI_Comm communicator = f.mesh->GetComm(); - const double stellarNorm = - gravity_prepared_test_utils::global_norm(stellarAction, communicator); + const double stellarNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator); - const double vacuumNorm = - gravity_prepared_test_utils::global_norm(vacuumAction, communicator); + const double vacuumNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator); - const double geometryChange = gravity_prepared_test_utils::relative_error( - deformedAction, stellarAction, communicator - ); + const double geometryChange = + gravity_prepared_test_utils::relative_error(deformedAction, stellarAction, communicator); INFO("Stellar action norm = " << stellarNorm); INFO("Vacuum action norm = " << vacuumNorm); @@ -361,37 +305,30 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Displacement Action Matches Centered Differences", - tags::barotrope &tags::closure &tags::hydro &tags::integration - &tags::jacobian &tags::mapping &tags::physics + tags::barotrope &tags::closure &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics + &tags::kernels ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.domainMapperStateless != nullptr); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - const mfem::Vector baseDensity = - barotropic_closure_geometry_test_utils::make_base_density(f); + const mfem::Vector baseDensity = barotropic_closure_geometry_test_utils::make_base_density(f); - const mfem::Vector baseEnthalpy = - barotropic_closure_geometry_test_utils::make_base_enthalpy(f); + const mfem::Vector baseEnthalpy = barotropic_closure_geometry_test_utils::make_base_enthalpy(f); - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.65); + const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.65); - constexpr double differenceStep = 1.0e-5; + constexpr double differenceStep = 1.0e-5; - const MPI_Comm communicator = f.mesh->GetComm(); + const MPI_Comm communicator = f.mesh->GetComm(); for (const double deformationScale : {0.0, 1.0}) { DYNAMIC_SECTION("Base deformation scale = " << deformationScale) { - const mfem::Vector baseDisplacement = - gravity_prepared_test_utils::make_displacement( - f, deformationScale - ); + const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, deformationScale); mfem::Vector plusDisplacement(baseDisplacement); @@ -406,50 +343,36 @@ TEST_CASE( mfem::Vector analyticAction; mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, baseDensity, - baseEnthalpy, plusDisplacement, plusResidual + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, plusDisplacement, plusResidual ); mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, baseDensity, - baseEnthalpy, minusDisplacement, minusResidual + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, minusDisplacement, minusResidual ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, - baseEnthalpy, baseDisplacement, displacementVariation, - analyticAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, + displacementVariation, analyticAction + ); mfem::Vector finiteDifference(plusResidual); finiteDifference -= minusResidual; finiteDifference *= 1.0 / (2.0 * differenceStep); - const double analyticNorm = - gravity_prepared_test_utils::global_norm( - analyticAction, communicator - ); + const double analyticNorm = gravity_prepared_test_utils::global_norm(analyticAction, communicator); const double finiteDifferenceNorm = - gravity_prepared_test_utils::global_norm( - finiteDifference, communicator - ); + gravity_prepared_test_utils::global_norm(finiteDifference, communicator); const double relativeError = - gravity_prepared_test_utils::relative_error( - analyticAction, finiteDifference, communicator - ); + gravity_prepared_test_utils::relative_error(analyticAction, finiteDifference, communicator); INFO("Base deformation scale = " << deformationScale); INFO("Analytic geometry-action norm = " << analyticNorm); - INFO( - "Finite-difference geometry-action norm = " - << finiteDifferenceNorm - ); + INFO("Finite-difference geometry-action norm = " << finiteDifferenceNorm); INFO("Geometry-action relative error = " << relativeError); @@ -463,34 +386,26 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Displacement Action Is Linear In Its Direction", - tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping - &tags::physics &tags::unit + tags::barotrope &tags::closure &tags::hydro &tags::jacobian &tags::mapping &tags::physics &tags::unit &tags::kernels ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.domainMapperStateless != nullptr); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - const mfem::Vector baseDensity = - barotropic_closure_geometry_test_utils::make_base_density(f); + const mfem::Vector baseDensity = barotropic_closure_geometry_test_utils::make_base_density(f); - const mfem::Vector baseEnthalpy = - barotropic_closure_geometry_test_utils::make_base_enthalpy(f); + const mfem::Vector baseEnthalpy = barotropic_closure_geometry_test_utils::make_base_enthalpy(f); - const mfem::Vector baseDisplacement = - gravity_prepared_test_utils::make_displacement(f, 0.8); + const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.8); - const mfem::Vector firstDirection = - gravity_prepared_test_utils::make_displacement(f, 0.4); + const mfem::Vector firstDirection = gravity_prepared_test_utils::make_displacement(f, 0.4); mfem::Vector secondDirection = - gravity_prepared_test_utils::make_deterministic_vector( - f.displacementFes->GetTrueVSize(), 0.91 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.91); secondDirection *= 0.01; @@ -511,29 +426,23 @@ TEST_CASE( mfem::Vector combinedAction; mfem::Vector zeroAction; - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - baseDisplacement, firstDirection, firstAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, firstDirection, firstAction + ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - baseDisplacement, secondDirection, secondAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, secondDirection, + secondAction + ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - baseDisplacement, combinedDirection, combinedAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, combinedDirection, + combinedAction + ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - baseDisplacement, zeroDirection, zeroAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, baseDisplacement, zeroDirection, zeroAction + ); mfem::Vector expectedAction(firstAction); @@ -543,15 +452,12 @@ TEST_CASE( const MPI_Comm communicator = f.mesh->GetComm(); - const double expectedNorm = - gravity_prepared_test_utils::global_norm(expectedAction, communicator); + const double expectedNorm = gravity_prepared_test_utils::global_norm(expectedAction, communicator); - const double linearityError = gravity_prepared_test_utils::relative_error( - combinedAction, expectedAction, communicator - ); + const double linearityError = + gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, communicator); - const double zeroActionNorm = - gravity_prepared_test_utils::global_norm(zeroAction, communicator); + const double zeroActionNorm = gravity_prepared_test_utils::global_norm(zeroAction, communicator); INFO("Expected combined-action norm = " << expectedNorm); @@ -568,55 +474,45 @@ TEST_CASE( TEST_CASE( "Barotropic Closure Displacement Action Excludes Vacuum", - tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics - &tags::unit + tags::barotrope &tags::closure &tags::hydro &tags::mapping &tags::physics &tags::unit &tags::kernels ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.domainMapperStateless != nullptr); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + const mean_field::eos::Polytrope barotrope(3.0, 1.5); - const mfem::Vector stellarDensity = - gravity_prepared_test_utils::make_domain_supported_density(f, true); + const mfem::Vector stellarDensity = gravity_prepared_test_utils::make_domain_supported_density(f, true); - const mfem::Vector vacuumDensity = - gravity_prepared_test_utils::make_domain_supported_density(f, false); + const mfem::Vector vacuumDensity = gravity_prepared_test_utils::make_domain_supported_density(f, false); mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize()); - zeroEnthalpy = 0.0; + zeroEnthalpy = 0.0; - const mfem::Vector baseDisplacement = - gravity_prepared_test_utils::make_displacement(f, 0.7); + const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.7); - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.5); + const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.5); mfem::Vector stellarAction; mfem::Vector vacuumAction; - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, stellarDensity, - zeroEnthalpy, baseDisplacement, displacementVariation, stellarAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, stellarDensity, zeroEnthalpy, baseDisplacement, displacementVariation, + stellarAction + ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy, - baseDisplacement, displacementVariation, vacuumAction - ); + mean_field::operators::kernels::apply_barotropic_closure_displacement_action( + f, *f.domainMapperStateless, barotrope, vacuumDensity, zeroEnthalpy, baseDisplacement, displacementVariation, + vacuumAction + ); const MPI_Comm communicator = f.mesh->GetComm(); - const double stellarNorm = - gravity_prepared_test_utils::global_norm(stellarAction, communicator); + const double stellarNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator); - const double vacuumNorm = - gravity_prepared_test_utils::global_norm(vacuumAction, communicator); + const double vacuumNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator); INFO("Stellar geometry-action norm = " << stellarNorm); diff --git a/tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp b/tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp index a7844a7..493c868 100644 --- a/tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp +++ b/tests/operators/kernels/hydrostatic_equilibrium_kernels.cpp @@ -34,8 +34,7 @@ namespace hydrostatic_kernel_test_utils { mfem::Vector make_enthalpy(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { - return 1.10 + 0.035 * position(0) - 0.021 * position(1) + - 0.014 * position(2); + return 1.10 + 0.035 * position(0) - 0.021 * position(1) + 0.014 * position(2); }); return project_scalar(*f.enthalpyFes, coefficient); @@ -43,8 +42,7 @@ namespace hydrostatic_kernel_test_utils { mfem::Vector make_potential(const mean_field::fem::FEM &f) { mfem::FunctionCoefficient coefficient([](const mfem::Vector &position) { - return -0.72 + 0.018 * position(0) + 0.011 * position(1) - - 0.025 * position(2); + return -0.72 + 0.018 * position(0) + 0.011 * position(1) - 0.025 * position(2); }); return project_scalar(*f.gravityPotentialFes, coefficient); @@ -98,23 +96,18 @@ namespace hydrostatic_kernel_test_utils { mfem::Vector difference(computed); difference -= reference; - return gravity_prepared_test_utils::global_norm( - difference, communicator - ) / + return gravity_prepared_test_utils::global_norm(difference, communicator) / std::max(normalization, std::numeric_limits::epsilon()); } - mfem::Vector - make_vacuum_supported_potential(const mean_field::fem::FEM &f) { + mfem::Vector make_vacuum_supported_potential(const mean_field::fem::FEM &f) { mfem::Vector attributeValues(f.mesh->attributes.Max()); - attributeValues = 0.0; + attributeValues = 0.0; - const int vacuumAttribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); - for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); - ++attributeIndex) { + for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) { const int attribute = f.mesh->attributes[attributeIndex]; if (attribute == vacuumAttribute) { @@ -144,10 +137,9 @@ namespace hydrostatic_kernel_test_utils { const mfem::Vector &input, mfem::Vector &output ) const override { - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_enthalpy_action( - f_, domainMapper_, input, displacementTrue_, output - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action( + f_, domainMapper_, input, displacementTrue_, output + ); } private: @@ -161,10 +153,9 @@ namespace hydrostatic_kernel_test_utils { TEST_CASE( "Rigid Rotation Potential Derivative Matches Centered Differences", - tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit + tags::barotrope &tags::hydro &tags::jacobian &tags::physics &tags::unit &tags::kernels ) { - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation(); mfem::Vector position(3); mfem::Vector direction(3); @@ -186,17 +177,12 @@ TEST_CASE( minusPosition.Add(-epsilon, direction); const double centeredDerivative = - (rotation.potential(plusPosition) - rotation.potential(minusPosition)) / - (2.0 * epsilon); + (rotation.potential(plusPosition) - rotation.potential(minusPosition)) / (2.0 * epsilon); - const double analyticDerivative = - rotation.potential_directional_derivative(position, direction); + const double analyticDerivative = rotation.potential_directional_derivative(position, direction); - const double relativeError = - std::abs(centeredDerivative - analyticDerivative) / - std::max( - std::abs(analyticDerivative), std::numeric_limits::epsilon() - ); + const double relativeError = std::abs(centeredDerivative - analyticDerivative) / + std::max(std::abs(analyticDerivative), std::numeric_limits::epsilon()); INFO("Rigid-rotation derivative error = " << relativeError); @@ -205,42 +191,31 @@ TEST_CASE( TEST_CASE( "Hydrostatic Residual Vanishes For A Manufactured Rotating State", - tags::barotrope &tags::hydro &tags::integration &tags::kernels - &tags::physics &tags::residuals + tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::physics &tags::residuals ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation(); - constexpr double bernoulliConstant = 0.73; - constexpr double potentialValue = -0.21; - constexpr double constantOffset = 0.40; + constexpr double bernoulliConstant = 0.73; + constexpr double potentialValue = -0.21; + constexpr double constantOffset = 0.40; - mfem::FunctionCoefficient enthalpyCoefficient( - [&rotation](const mfem::Vector &position) { - return bernoulliConstant - potentialValue + - rotation.potential(position); - } - ); + mfem::FunctionCoefficient enthalpyCoefficient([&rotation](const mfem::Vector &position) { + return bernoulliConstant - potentialValue + rotation.potential(position); + }); const mfem::Vector interpolatedEnthalpy = - hydrostatic_kernel_test_utils::project_scalar( - *f.enthalpyFes, enthalpyCoefficient - ); + hydrostatic_kernel_test_utils::project_scalar(*f.enthalpyFes, enthalpyCoefficient); const mfem::Vector potential = - hydrostatic_kernel_test_utils::make_constant_field( - *f.gravityPotentialFes, potentialValue - ); + hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.0); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.0); - const MPI_Comm communicator = f.mesh->GetComm(); + const MPI_Comm communicator = f.mesh->GetComm(); /* * First measure the residual of the nodally interpolated @@ -253,35 +228,26 @@ TEST_CASE( mfem::Vector interpolatedReferenceResidual; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, - displacement, bernoulliConstant, interpolatedResidual + f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement, bernoulliConstant, + interpolatedResidual ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, - displacement, bernoulliConstant + constantOffset, - interpolatedReferenceResidual + f, *f.domainMapperStateless, rotation, interpolatedEnthalpy, potential, displacement, + bernoulliConstant + constantOffset, interpolatedReferenceResidual ); const double interpolatedResidualNorm = - gravity_prepared_test_utils::global_norm( - interpolatedResidual, communicator - ); + gravity_prepared_test_utils::global_norm(interpolatedResidual, communicator); const double interpolatedReferenceNorm = - gravity_prepared_test_utils::global_norm( - interpolatedReferenceResidual, communicator - ); + gravity_prepared_test_utils::global_norm(interpolatedReferenceResidual, communicator); REQUIRE(interpolatedReferenceNorm > 1.0e-12); - const double representationFloor = - interpolatedResidualNorm / interpolatedReferenceNorm; + const double representationFloor = interpolatedResidualNorm / interpolatedReferenceNorm; - INFO( - "Interpolated rotating-state residual norm = " - << interpolatedResidualNorm - ); + INFO("Interpolated rotating-state residual norm = " << interpolatedResidualNorm); INFO( "Interpolated rotating-state relative " @@ -310,8 +276,9 @@ TEST_CASE( * side is in the range of M_h. Starting CG from zero keeps the * iteration in the active stellar subspace. */ - hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator - enthalpyMassOperator(f, *f.domainMapperStateless, displacement); + hydrostatic_kernel_test_utils::HydrostaticEnthalpyMassOperator enthalpyMassOperator( + f, *f.domainMapperStateless, displacement + ); mfem::Vector correctionRightHandSide(interpolatedResidual); @@ -332,20 +299,11 @@ TEST_CASE( projectionSolver.Mult(correctionRightHandSide, enthalpyCorrection); - INFO( - "Discrete-equilibrium projection converged = " - << projectionSolver.GetConverged() - ); + INFO("Discrete-equilibrium projection converged = " << projectionSolver.GetConverged()); - INFO( - "Discrete-equilibrium projection iterations = " - << projectionSolver.GetNumIterations() - ); + INFO("Discrete-equilibrium projection iterations = " << projectionSolver.GetNumIterations()); - INFO( - "Discrete-equilibrium projection final norm = " - << projectionSolver.GetFinalNorm() - ); + INFO("Discrete-equilibrium projection final norm = " << projectionSolver.GetFinalNorm()); REQUIRE(projectionSolver.GetConverged()); @@ -356,9 +314,7 @@ TEST_CASE( correctionEquationResidual -= correctionRightHandSide; const double correctionEquationNorm = - gravity_prepared_test_utils::global_norm( - correctionEquationResidual, communicator - ); + gravity_prepared_test_utils::global_norm(correctionEquationResidual, communicator); INFO( "Discrete-equilibrium correction-equation " @@ -366,10 +322,7 @@ TEST_CASE( << correctionEquationNorm ); - CHECK( - correctionEquationNorm <= - std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15) - ); + CHECK(correctionEquationNorm <= std::max(5.0e-12 * interpolatedResidualNorm, 5.0e-15)); mfem::Vector discreteEnthalpy(interpolatedEnthalpy); @@ -379,25 +332,20 @@ TEST_CASE( mfem::Vector referenceResidual; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, - displacement, bernoulliConstant, exactResidual + f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement, bernoulliConstant, + exactResidual ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, - displacement, bernoulliConstant + constantOffset, referenceResidual + f, *f.domainMapperStateless, rotation, discreteEnthalpy, potential, displacement, + bernoulliConstant + constantOffset, referenceResidual ); - const double exactNorm = - gravity_prepared_test_utils::global_norm(exactResidual, communicator); + const double exactNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator); - const double referenceNorm = gravity_prepared_test_utils::global_norm( - referenceResidual, communicator - ); + const double referenceNorm = gravity_prepared_test_utils::global_norm(referenceResidual, communicator); - const double correctionNorm = gravity_prepared_test_utils::global_norm( - enthalpyCorrection, communicator - ); + const double correctionNorm = gravity_prepared_test_utils::global_norm(enthalpyCorrection, communicator); INFO("Enthalpy representation correction norm = " << correctionNorm); @@ -412,43 +360,31 @@ TEST_CASE( TEST_CASE( "Exact Constant Hydrostatic Equilibrium Remains Zero Under Deformation", - tags::barotrope &tags::hydro &tags::integration &tags::jacobian - &tags::kernels &tags::mapping &tags::physics + tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_zero_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation(); - constexpr double enthalpyValue = 1.20; - constexpr double potentialValue = -0.35; + constexpr double enthalpyValue = 1.20; + constexpr double potentialValue = -0.35; - constexpr double bernoulliConstant = enthalpyValue + potentialValue; + constexpr double bernoulliConstant = enthalpyValue + potentialValue; - const mfem::Vector enthalpy = - hydrostatic_kernel_test_utils::make_constant_field( - *f.enthalpyFes, enthalpyValue - ); + const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_constant_field(*f.enthalpyFes, enthalpyValue); const mfem::Vector potential = - hydrostatic_kernel_test_utils::make_constant_field( - *f.gravityPotentialFes, potentialValue - ); + hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, potentialValue); - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.67); + const mfem::Vector displacementVariation = gravity_prepared_test_utils::make_displacement(f, 0.67); - const MPI_Comm communicator = f.mesh->GetComm(); + 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 = - gravity_prepared_test_utils::make_displacement( - f, deformationScale - ); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, deformationScale); mfem::Vector exactResidual; mfem::Vector referenceResidual; @@ -456,48 +392,35 @@ TEST_CASE( mfem::Vector referenceGeometryAction; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, exactResidual + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, + exactResidual ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant + 0.50, referenceResidual + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50, + referenceResidual ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, displacementVariation, - exactGeometryAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, + displacementVariation, exactGeometryAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant + 0.50, - displacementVariation, referenceGeometryAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant + 0.50, + displacementVariation, referenceGeometryAction + ); - const double exactResidualNorm = - gravity_prepared_test_utils::global_norm( - exactResidual, communicator - ); + const double exactResidualNorm = gravity_prepared_test_utils::global_norm(exactResidual, communicator); const double referenceResidualNorm = - gravity_prepared_test_utils::global_norm( - referenceResidual, communicator - ); + gravity_prepared_test_utils::global_norm(referenceResidual, communicator); const double exactGeometryNorm = - gravity_prepared_test_utils::global_norm( - exactGeometryAction, communicator - ); + gravity_prepared_test_utils::global_norm(exactGeometryAction, communicator); const double referenceGeometryNorm = - gravity_prepared_test_utils::global_norm( - referenceGeometryAction, communicator - ); + gravity_prepared_test_utils::global_norm(referenceGeometryAction, communicator); REQUIRE(referenceResidualNorm > 1.0e-12); @@ -512,64 +435,49 @@ TEST_CASE( TEST_CASE( "Hydrostatic Equilibrium Excludes Vacuum Elements", - tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics - &tags::unit + tags::barotrope &tags::hydro &tags::kernels &tags::mapping &tags::physics &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_zero_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_zero_rotation(); const mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize()); mfem::Vector enthalpy(zeroEnthalpy); - enthalpy = 0.0; + enthalpy = 0.0; - const mfem::Vector vacuumPotential = - hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f); + const mfem::Vector vacuumPotential = hydrostatic_kernel_test_utils::make_vacuum_supported_potential(f); const mfem::Vector stellarPotential = - hydrostatic_kernel_test_utils::make_constant_field( - *f.gravityPotentialFes, 1.0 - ); + hydrostatic_kernel_test_utils::make_constant_field(*f.gravityPotentialFes, 1.0); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0); mfem::Vector residual; mfem::Vector vacuumAction; mfem::Vector stellarAction; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential, - displacement, 0.0, residual + f, *f.domainMapperStateless, rotation, enthalpy, vacuumPotential, displacement, 0.0, residual ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_potential_action( - f, *f.domainMapperStateless, vacuumPotential, displacement, - vacuumAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action( + f, *f.domainMapperStateless, vacuumPotential, displacement, vacuumAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_potential_action( - f, *f.domainMapperStateless, stellarPotential, displacement, - stellarAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action( + f, *f.domainMapperStateless, stellarPotential, displacement, stellarAction + ); - const MPI_Comm communicator = f.mesh->GetComm(); + const MPI_Comm communicator = f.mesh->GetComm(); - const double residualNorm = - gravity_prepared_test_utils::global_norm(residual, communicator); + const double residualNorm = gravity_prepared_test_utils::global_norm(residual, communicator); - const double vacuumActionNorm = - gravity_prepared_test_utils::global_norm(vacuumAction, communicator); + const double vacuumActionNorm = gravity_prepared_test_utils::global_norm(vacuumAction, communicator); - const double stellarActionNorm = - gravity_prepared_test_utils::global_norm(stellarAction, communicator); + const double stellarActionNorm = gravity_prepared_test_utils::global_norm(stellarAction, communicator); REQUIRE(stellarActionNorm > 1.0e-12); @@ -580,40 +488,28 @@ TEST_CASE( TEST_CASE( "Hydrostatic Jacobian Matches Blocks And Centered Differences", - tags::barotrope &tags::hydro &tags::integration &tags::jacobian - &tags::kernels &tags::mapping &tags::physics + tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::kernels &tags::mapping &tags::physics ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation(); - const mfem::Vector enthalpy = - hydrostatic_kernel_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f); - const mfem::Vector potential = - hydrostatic_kernel_test_utils::make_potential(f); + const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0); const mfem::Vector enthalpyVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.23 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.23); const mfem::Vector potentialVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.47 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.47); const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.displacementFes->GetTrueVSize(), 0.71 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.71); constexpr double bernoulliConstant = 0.41; constexpr double constantVariation = -0.37; @@ -625,35 +521,26 @@ TEST_CASE( mfem::Vector displacementAction; mfem::Vector completeAction; - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_enthalpy_action( - f, *f.domainMapperStateless, enthalpyVariation, displacement, - enthalpyAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_enthalpy_action( + f, *f.domainMapperStateless, enthalpyVariation, displacement, enthalpyAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_potential_action( - f, *f.domainMapperStateless, potentialVariation, displacement, - potentialAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_potential_action( + f, *f.domainMapperStateless, potentialVariation, displacement, potentialAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_constant_action( - f, *f.domainMapperStateless, constantVariation, displacement, - constantAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_constant_action( + f, *f.domainMapperStateless, constantVariation, displacement, constantAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, displacementVariation, - displacementAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, + displacementVariation, displacementAction + ); mean_field::operators::kernels::apply_hydrostatic_equilibrium_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, enthalpyVariation, potentialVariation, - constantVariation, displacementVariation, completeAction + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, enthalpyVariation, + potentialVariation, constantVariation, displacementVariation, completeAction ); mfem::Vector blockAction(enthalpyAction); @@ -663,25 +550,21 @@ TEST_CASE( const MPI_Comm communicator = f.mesh->GetComm(); - const double blockError = gravity_prepared_test_utils::relative_error( - completeAction, blockAction, communicator - ); + const double blockError = gravity_prepared_test_utils::relative_error(completeAction, blockAction, communicator); INFO("Hydrostatic block reconstruction error = " << blockError); CHECK(blockError < 5.0e-13); auto evaluate_residual = [&f, &rotation]( - const mfem::Vector &trialEnthalpy, - const mfem::Vector &trialPotential, - const mfem::Vector &trialDisplacement, - const double trialConstant + const mfem::Vector &trialEnthalpy, const mfem::Vector &trialPotential, + const mfem::Vector &trialDisplacement, const double trialConstant ) { mfem::Vector residual; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, trialEnthalpy, - trialPotential, trialDisplacement, trialConstant, residual + f, *f.domainMapperStateless, rotation, trialEnthalpy, trialPotential, trialDisplacement, trialConstant, + residual ); return residual; @@ -694,16 +577,10 @@ TEST_CASE( minusEnthalpy.Add(-epsilon, enthalpyVariation); - const mfem::Vector enthalpyDifference = - hydrostatic_kernel_test_utils::centered_difference( - evaluate_residual( - plusEnthalpy, potential, displacement, bernoulliConstant - ), - evaluate_residual( - minusEnthalpy, potential, displacement, bernoulliConstant - ), - epsilon - ); + const mfem::Vector enthalpyDifference = hydrostatic_kernel_test_utils::centered_difference( + evaluate_residual(plusEnthalpy, potential, displacement, bernoulliConstant), + evaluate_residual(minusEnthalpy, potential, displacement, bernoulliConstant), epsilon + ); mfem::Vector plusPotential(potential); mfem::Vector minusPotential(potential); @@ -712,29 +589,15 @@ TEST_CASE( minusPotential.Add(-epsilon, potentialVariation); - const mfem::Vector potentialDifference = - hydrostatic_kernel_test_utils::centered_difference( - evaluate_residual( - enthalpy, plusPotential, displacement, bernoulliConstant - ), - evaluate_residual( - enthalpy, minusPotential, displacement, bernoulliConstant - ), - epsilon - ); + const mfem::Vector potentialDifference = hydrostatic_kernel_test_utils::centered_difference( + evaluate_residual(enthalpy, plusPotential, displacement, bernoulliConstant), + evaluate_residual(enthalpy, minusPotential, displacement, bernoulliConstant), epsilon + ); - const mfem::Vector constantDifference = - hydrostatic_kernel_test_utils::centered_difference( - evaluate_residual( - enthalpy, potential, displacement, - bernoulliConstant + epsilon * constantVariation - ), - evaluate_residual( - enthalpy, potential, displacement, - bernoulliConstant - epsilon * constantVariation - ), - epsilon - ); + const mfem::Vector constantDifference = hydrostatic_kernel_test_utils::centered_difference( + evaluate_residual(enthalpy, potential, displacement, bernoulliConstant + epsilon * constantVariation), + evaluate_residual(enthalpy, potential, displacement, bernoulliConstant - epsilon * constantVariation), epsilon + ); mfem::Vector plusDisplacement(displacement); mfem::Vector minusDisplacement(displacement); @@ -743,33 +606,22 @@ TEST_CASE( minusDisplacement.Add(-epsilon, displacementVariation); - const mfem::Vector displacementDifference = - hydrostatic_kernel_test_utils::centered_difference( - evaluate_residual( - enthalpy, potential, plusDisplacement, bernoulliConstant - ), - evaluate_residual( - enthalpy, potential, minusDisplacement, bernoulliConstant - ), - epsilon - ); - - const double enthalpyError = gravity_prepared_test_utils::relative_error( - enthalpyAction, enthalpyDifference, communicator + const mfem::Vector displacementDifference = hydrostatic_kernel_test_utils::centered_difference( + evaluate_residual(enthalpy, potential, plusDisplacement, bernoulliConstant), + evaluate_residual(enthalpy, potential, minusDisplacement, bernoulliConstant), epsilon ); - const double potentialError = gravity_prepared_test_utils::relative_error( - potentialAction, potentialDifference, communicator - ); + const double enthalpyError = + gravity_prepared_test_utils::relative_error(enthalpyAction, enthalpyDifference, communicator); - const double constantError = gravity_prepared_test_utils::relative_error( - constantAction, constantDifference, communicator - ); + const double potentialError = + gravity_prepared_test_utils::relative_error(potentialAction, potentialDifference, communicator); + + const double constantError = + gravity_prepared_test_utils::relative_error(constantAction, constantDifference, communicator); const double displacementError = - gravity_prepared_test_utils::relative_error( - displacementAction, displacementDifference, communicator - ); + gravity_prepared_test_utils::relative_error(displacementAction, displacementDifference, communicator); INFO("Hydrostatic enthalpy-block error = " << enthalpyError); @@ -803,35 +655,26 @@ TEST_CASE( combinedMinusDisplacement.Add(-epsilon, displacementVariation); - const mfem::Vector combinedDifference = - hydrostatic_kernel_test_utils::centered_difference( - evaluate_residual( - combinedPlusEnthalpy, combinedPlusPotential, - combinedPlusDisplacement, - bernoulliConstant + epsilon * constantVariation - ), - evaluate_residual( - combinedMinusEnthalpy, combinedMinusPotential, - combinedMinusDisplacement, - bernoulliConstant - epsilon * constantVariation - ), - epsilon - ); + const mfem::Vector combinedDifference = hydrostatic_kernel_test_utils::centered_difference( + evaluate_residual( + combinedPlusEnthalpy, combinedPlusPotential, combinedPlusDisplacement, + bernoulliConstant + epsilon * constantVariation + ), + evaluate_residual( + combinedMinusEnthalpy, combinedMinusPotential, combinedMinusDisplacement, + bernoulliConstant - epsilon * constantVariation + ), + epsilon + ); - const double blockNormSum = - gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) + - gravity_prepared_test_utils::global_norm( - potentialAction, communicator - ) + - gravity_prepared_test_utils::global_norm(constantAction, communicator) + - gravity_prepared_test_utils::global_norm( - displacementAction, communicator - ); + const double blockNormSum = gravity_prepared_test_utils::global_norm(enthalpyAction, communicator) + + gravity_prepared_test_utils::global_norm(potentialAction, communicator) + + gravity_prepared_test_utils::global_norm(constantAction, communicator) + + gravity_prepared_test_utils::global_norm(displacementAction, communicator); - const double simultaneousError = - hydrostatic_kernel_test_utils::sum_normalized_error( - completeAction, combinedDifference, blockNormSum, communicator - ); + const double simultaneousError = hydrostatic_kernel_test_utils::sum_normalized_error( + completeAction, combinedDifference, blockNormSum, communicator + ); INFO("Hydrostatic simultaneous Jacobian error = " << simultaneousError); @@ -840,75 +683,58 @@ TEST_CASE( TEST_CASE( "Hydrostatic Displacement Action Is Linear In Its Direction", - tags::barotrope &tags::hydro &tags::integration &tags::jacobian - &tags::mapping &tags::physics &tags::unit + tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mapping &tags::physics &tags::unit + &tags::kernels ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - const mean_field::physics::RigidRotation rotation = - hydrostatic_kernel_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = hydrostatic_kernel_test_utils::make_rotation(); - const mfem::Vector enthalpy = - hydrostatic_kernel_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f); - const mfem::Vector potential = - hydrostatic_kernel_test_utils::make_potential(f); + const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 1.0); const mfem::Vector firstDirection = - gravity_prepared_test_utils::make_deterministic_vector( - f.displacementFes->GetTrueVSize(), 0.31 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.31); const mfem::Vector secondDirection = - gravity_prepared_test_utils::make_deterministic_vector( - f.displacementFes->GetTrueVSize(), 0.83 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.displacementFes->GetTrueVSize(), 0.83); constexpr double firstScale = 0.43; constexpr double secondScale = -0.29; constexpr double bernoulliConstant = 0.41; const mfem::Vector combinedDirection = - gravity_prepared_test_utils::linear_combination( - firstDirection, firstScale, secondDirection, secondScale - ); + gravity_prepared_test_utils::linear_combination(firstDirection, firstScale, secondDirection, secondScale); mfem::Vector firstAction; mfem::Vector secondAction; mfem::Vector combinedAction; - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, firstDirection, firstAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, firstDirection, + firstAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, secondDirection, secondAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, secondDirection, + secondAction + ); - mean_field::operators::kernels:: - apply_hydrostatic_equilibrium_displacement_action( - f, *f.domainMapperStateless, rotation, enthalpy, potential, - displacement, bernoulliConstant, combinedDirection, combinedAction - ); + mean_field::operators::kernels::apply_hydrostatic_equilibrium_displacement_action( + f, *f.domainMapperStateless, rotation, enthalpy, potential, displacement, bernoulliConstant, combinedDirection, + combinedAction + ); const mfem::Vector expectedAction = - gravity_prepared_test_utils::linear_combination( - firstAction, firstScale, secondAction, secondScale - ); + gravity_prepared_test_utils::linear_combination(firstAction, firstScale, secondAction, secondScale); - const double linearityError = gravity_prepared_test_utils::relative_error( - combinedAction, expectedAction, f.mesh->GetComm() - ); + const double linearityError = + gravity_prepared_test_utils::relative_error(combinedAction, expectedAction, f.mesh->GetComm()); INFO("Hydrostatic displacement-linearity error = " << linearityError); @@ -917,13 +743,11 @@ TEST_CASE( TEST_CASE( "Hydrostatic Residual Is Translationally Invariant On Deformed Geometry", - tags::barotrope &tags::hydro &tags::integration &tags::kernels - &tags::mapping &tags::physics &tags::residuals + tags::barotrope &tags::hydro &tags::integration &tags::kernels &tags::mapping &tags::physics &tags::residuals ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); mfem::Vector angularVelocity(3); @@ -946,33 +770,25 @@ TEST_CASE( mfem::Vector translatedCenter(center); translatedCenter += translation; - const mean_field::physics::RigidRotation baseRotation( - angularVelocity, center - ); + const mean_field::physics::RigidRotation baseRotation(angularVelocity, center); - const mean_field::physics::RigidRotation translatedRotation( - angularVelocity, translatedCenter - ); + const mean_field::physics::RigidRotation translatedRotation(angularVelocity, translatedCenter); - const mfem::Vector enthalpy = - hydrostatic_kernel_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = hydrostatic_kernel_test_utils::make_enthalpy(f); - const mfem::Vector potential = - hydrostatic_kernel_test_utils::make_potential(f); + const mfem::Vector potential = hydrostatic_kernel_test_utils::make_potential(f); /* * Use a nontrivially deformed base state so this checks rotation * and mapped geometry simultaneously. The comparison state adds * an exactly representable rigid translation to that deformation. */ - const mfem::Vector baseDisplacement = - gravity_prepared_test_utils::make_displacement(f, 0.73); + const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.73); mfem::ParGridFunction translationField(f.displacementFes.get()); mfem::VectorFunctionCoefficient translationCoefficient( - f.mesh->Dimension(), - [&translation](const mfem::Vector &, mfem::Vector &value) { + f.mesh->Dimension(), [&translation](const mfem::Vector &, mfem::Vector &value) { value.SetSize(translation.Size()); value = translation; } @@ -994,13 +810,13 @@ TEST_CASE( mfem::Vector untranslatedCenterResidual; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, baseRotation, enthalpy, potential, - baseDisplacement, bernoulliConstant, baseResidual + f, *f.domainMapperStateless, baseRotation, enthalpy, potential, baseDisplacement, bernoulliConstant, + baseResidual ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, translatedRotation, enthalpy, potential, - translatedDisplacement, bernoulliConstant, translatedResidual + f, *f.domainMapperStateless, translatedRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant, + translatedResidual ); /* @@ -1008,43 +824,29 @@ TEST_CASE( * center fixed. This must not agree with the covariant result. */ mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, baseRotation, enthalpy, potential, - translatedDisplacement, bernoulliConstant, untranslatedCenterResidual + f, *f.domainMapperStateless, baseRotation, enthalpy, potential, translatedDisplacement, bernoulliConstant, + untranslatedCenterResidual ); - const MPI_Comm communicator = f.mesh->GetComm(); + const MPI_Comm communicator = f.mesh->GetComm(); - const double baseResidualNorm = - gravity_prepared_test_utils::global_norm(baseResidual, communicator); + const double baseResidualNorm = gravity_prepared_test_utils::global_norm(baseResidual, communicator); - const double translatedResidualNorm = - gravity_prepared_test_utils::global_norm( - translatedResidual, communicator - ); + const double translatedResidualNorm = gravity_prepared_test_utils::global_norm(translatedResidual, communicator); const double translationInvarianceError = - gravity_prepared_test_utils::relative_error( - translatedResidual, baseResidual, communicator - ); + gravity_prepared_test_utils::relative_error(translatedResidual, baseResidual, communicator); const double fixedCenterDifference = - gravity_prepared_test_utils::relative_error( - untranslatedCenterResidual, translatedResidual, communicator - ); + gravity_prepared_test_utils::relative_error(untranslatedCenterResidual, translatedResidual, communicator); INFO("Base deformed hydrostatic residual norm = " << baseResidualNorm); INFO("Translated hydrostatic residual norm = " << translatedResidualNorm); - INFO( - "Mapped-rotation translation invariance error = " - << translationInvarianceError - ); + INFO("Mapped-rotation translation invariance error = " << translationInvarianceError); - INFO( - "Relative change with untranslated rotation center = " - << fixedCenterDifference - ); + INFO("Relative change with untranslated rotation center = " << fixedCenterDifference); REQUIRE(baseResidualNorm > 1.0e-12); REQUIRE(translatedResidualNorm > 1.0e-12); diff --git a/tests/operators/kernels/pressure_force_kernels.cpp b/tests/operators/kernels/pressure_force_kernels.cpp index 7c6bb97..35c4b54 100644 --- a/tests/operators/kernels/pressure_force_kernels.cpp +++ b/tests/operators/kernels/pressure_force_kernels.cpp @@ -1,6 +1,6 @@ +#include #include #include -#include #include @@ -19,44 +19,34 @@ namespace pressure_force_kernel_test_utils { 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); + 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) { + [[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); + [[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 - ); + 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 - ); + mean_field::utils::populate_domain_tdofs(f.enthalpyFes.get(), stellarElementMask, stellarEnthalpyTrueDofs); - for (int listIndex = 0; listIndex < stellarEnthalpyTrueDofs.Size(); - ++listIndex) { + 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." + trueDof >= 0 && trueDof < enthalpyTrue.Size(), "The stellar enthalpy true-DOF mask contains an " + "invalid index." ); enthalpyTrue(trueDof) = 0.0; @@ -65,11 +55,9 @@ namespace pressure_force_kernel_test_utils { return enthalpyTrue; } - [[nodiscard]] mfem::Vector - make_positive_asymmetric_enthalpy(const mean_field::fem::FEM &f) { + [[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); + return 1.10 + 0.07 * position(0) - 0.04 * position(1) + 0.03 * position(2); }); mfem::ParGridFunction enthalpyField(f.enthalpyFes.get()); @@ -89,15 +77,9 @@ namespace pressure_force_kernel_test_utils { ) { const int dimension = f.mesh->Dimension(); - MFEM_VERIFY( - component >= 0 && component < dimension, - "The requested vector component is invalid." - ); + MFEM_VERIFY(component >= 0 && component < dimension, "The requested vector component is invalid."); - MFEM_VERIFY( - coordinate >= -1 && coordinate < dimension, - "The requested coordinate is invalid." - ); + MFEM_VERIFY(coordinate >= -1 && coordinate < dimension, "The requested coordinate is invalid."); /* * coordinate == -1 gives the rigid translation e_component. @@ -107,9 +89,7 @@ namespace pressure_force_kernel_test_utils { * w = x_coordinate e_component. */ mfem::VectorFunctionCoefficient coefficient( - dimension, - [component, coordinate, - dimension](const mfem::Vector &position, mfem::Vector &value) { + dimension, [component, coordinate, dimension](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(dimension); value = 0.0; @@ -132,20 +112,192 @@ namespace pressure_force_kernel_test_utils { const mfem::Vector &right, MPI_Comm communicator ) { - MFEM_VERIFY( - left.Size() == right.Size(), - "The global dot-product vectors have different sizes." - ); + 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 - ); + MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator); return globalDot; } + + [[nodiscard]] double integrate_pressure( + const mean_field::fem::FEM &f, + const mean_field::mapping::DomainMapperStateless &domainMapper, + const mean_field::eos::Polytrope &barotrope, + const mfem::Vector &enthalpyTrue, + const mfem::Vector &displacementTrue + ) { + MFEM_VERIFY( + enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(), + "The pressure-integral enthalpy vector has the wrong size." + ); + + MFEM_VERIFY( + displacementTrue.Size() == f.displacementFes->GetTrueVSize(), + "The pressure-integral displacement vector has the wrong size." + ); + + mfem::Vector enthalpyLocal(f.enthalpyFes->GetVSize()); + + const mfem::Operator *enthalpyProlongation = f.enthalpyFes->GetProlongationMatrix(); + + if (enthalpyProlongation != nullptr) { + enthalpyProlongation->Mult(enthalpyTrue, enthalpyLocal); + } else { + enthalpyLocal = enthalpyTrue; + } + + mfem::Vector displacementLocal(f.displacementFes->GetVSize()); + + const mfem::Operator *displacementProlongation = f.displacementFes->GetProlongationMatrix(); + + if (displacementProlongation != nullptr) { + displacementProlongation->Mult(displacementTrue, displacementLocal); + } else { + displacementLocal = displacementTrue; + } + + const double pressureExtraOrderValue = + barotrope.polytropic_index() * static_cast(mean_field::field::Enthalpy::Scalar::familyOrder); + + MFEM_VERIFY( + std::isfinite(pressureExtraOrderValue) && pressureExtraOrderValue >= 0.0 && + pressureExtraOrderValue <= static_cast(std::numeric_limits::max()), + "The pressure-integral EOS order is invalid." + ); + + const int pressureExtraOrder = static_cast(std::ceil(pressureExtraOrderValue)); + + using EnthalpyField = mean_field::field::Field; + + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); + + mean_field::mapping::VolumeMappingContext mappingContext; + + mfem::Array enthalpyDofs; + mfem::Array displacementDofs; + mfem::Array compactificationDofs; + + mfem::Vector elementEnthalpy; + mfem::Vector elementDisplacement; + mfem::Vector elementCompactification; + mfem::Vector enthalpyShape; + + double localPressureIntegral = 0.0; + + const int vacuumAttribute = domainMapper.GetVacuumElementAttribute(); + + for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); + + MFEM_VERIFY( + transformation != nullptr, "The pressure-integral reference received a null " + "element transformation." + ); + + if (transformation->Attribute == vacuumAttribute) { + continue; + } + + const mfem::FiniteElement &enthalpyElement = *f.enthalpyFes->GetFE(elementId); + + const mfem::FiniteElement &displacementElement = *f.displacementFes->GetFE(elementId); + + const mfem::FiniteElement &compactificationElement = *f.compactificationFes->GetFE(elementId); + + mfem::DofTransformation *enthalpyDofTransformation = f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs); + + mfem::DofTransformation *displacementDofTransformation = + f.displacementFes->GetElementVDofs(elementId, displacementDofs); + + mfem::DofTransformation *compactificationDofTransformation = + f.compactificationFes->GetElementDofs(elementId, compactificationDofs); + + enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy); + + displacementLocal.GetSubVector(displacementDofs, elementDisplacement); + + f.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification); + + if (enthalpyDofTransformation != nullptr) { + enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy); + } + + if (displacementDofTransformation != nullptr) { + displacementDofTransformation->InvTransformPrimal(elementDisplacement); + } + + if (compactificationDofTransformation != nullptr) { + compactificationDofTransformation->InvTransformPrimal(elementCompactification); + } + + const mean_field::mapping::ElementDisplacementData displacementData = + mean_field::mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement); + + const mean_field::mapping::ElementCompactificationData compactificationData( + compactificationElement, elementCompactification + ); + + const mean_field::mapping::ElementMappingData mappingData{ + .displacement = displacementData, .compactification = compactificationData + }; + + const mean_field::quadrature::Query query = + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::diagnostic, transformation->OrderW(), + std::array{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR, + mean_field::quadrature::MappingKind::general + ); + + const mean_field::quadrature::MfemRule rule = + f.quadratureFactory->get(query, transformation->GetGeometryType()); + + MFEM_VERIFY(rule.integration_rule != nullptr, "The pressure-integral quadrature rule is null."); + + enthalpyShape.SetSize(enthalpyElement.GetDof()); + + for (int quadratureIndex = 0; quadratureIndex < rule.integration_rule->GetNPoints(); ++quadratureIndex) { + const mfem::IntegrationPoint &integrationPoint = rule.integration_rule->IntPoint(quadratureIndex); + + transformation->SetIntPoint(&integrationPoint); + + const mean_field::mapping::MappingStatus mappingStatus = domainMapper.EvaluateVolume( + mappingData, *transformation, integrationPoint, workspace, mappingContext + ); + + MFEM_VERIFY( + mappingStatus == mean_field::mapping::MappingStatus::valid, + "Stateless mapping failed in the " + "independent pressure integral. Element: " + << elementId << ", attribute: " << transformation->Attribute + << ", quadrature point: " << quadratureIndex << ", status: " << static_cast(mappingStatus) + ); + + enthalpyElement.CalcShape(integrationPoint, enthalpyShape); + + const double enthalpyValue = elementEnthalpy * enthalpyShape; + + const double pressureValue = barotrope.pressure_from_enthalpy(enthalpyValue); + + const double contribution = pressureValue * mappingContext.quadrature.weight; + + MFEM_VERIFY( + std::isfinite(pressureValue) && std::isfinite(contribution), "The independent pressure integral " + "encountered a non-finite value." + ); + + localPressureIntegral += contribution; + } + } + + double globalPressureIntegral = 0.0; + + MPI_Allreduce(&localPressureIntegral, &globalPressureIntegral, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm()); + + return globalPressureIntegral; + } } // namespace pressure_force_kernel_test_utils TEST_CASE( @@ -154,31 +306,26 @@ TEST_CASE( ) { mean_field::utils::Args args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + 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 mean_field::eos::Polytrope barotrope(3.0, 0.25); mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize()); - enthalpyTrue = 0.0; + enthalpyTrue = 0.0; - const mfem::Vector displacementTrue = - pressure_force_kernel_test_utils::make_zero_displacement(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 + 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() - ); + const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm()); CHECK(residualNorm == 0.0); } @@ -189,19 +336,15 @@ TEST_CASE( ) { mean_field::utils::Args args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + 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 mean_field::eos::Polytrope barotrope(3.0, 0.25); - const mfem::Vector enthalpyTrue = - pressure_force_kernel_test_utils::make_vacuum_only_enthalpy(f); + 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() - ); + const double enthalpyNorm = gravity_prepared_test_utils::global_norm(enthalpyTrue, f.mesh->GetComm()); /* * Ensure this is a real exclusion test rather than another @@ -209,21 +352,17 @@ TEST_CASE( */ REQUIRE(enthalpyNorm > 0.0); - const mfem::Vector displacementTrue = - pressure_force_kernel_test_utils::make_zero_displacement(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 + 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() - ); + const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm()); CHECK(residualNorm == 0.0); } @@ -234,12 +373,11 @@ TEST_CASE( ) { mean_field::utils::Args args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + 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 mean_field::eos::Polytrope barotrope(3.0, 0.25); /* * With n = 3 and K = 1/4: @@ -247,21 +385,17 @@ TEST_CASE( * P(1) = 1/4. */ mfem::Vector enthalpyTrue(f.enthalpyFes->GetTrueVSize()); - enthalpyTrue = 1.0; + enthalpyTrue = 1.0; - const mfem::Vector displacementTrue = - pressure_force_kernel_test_utils::make_zero_displacement(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 + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); - const double residualNorm = gravity_prepared_test_utils::global_norm( - residualTrue, f.mesh->GetComm() - ); + const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm()); INFO("Positive-pressure residual norm = " << residualNorm); @@ -272,36 +406,29 @@ TEST_CASE( TEST_CASE( "Pressure Force Residual Does No Work Against Rigid Translations", - tags::barotrope &tags::pressure &tags::kernels &tags::integration - &tags::accuracy + 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); + 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 mean_field::eos::Polytrope barotrope(3.0, 0.25); - const mfem::Vector enthalpyTrue = - pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f); + 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); + 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 + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); - const double residualNorm = gravity_prepared_test_utils::global_norm( - residualTrue, f.mesh->GetComm() - ); + const double residualNorm = gravity_prepared_test_utils::global_norm(residualTrue, f.mesh->GetComm()); REQUIRE(residualNorm > 0.0); @@ -309,21 +436,14 @@ TEST_CASE( for (int component = 0; component < dimension; ++component) { const mfem::Vector translationTrue = - pressure_force_kernel_test_utils::make_component_test_field( - f, component, -1 - ); + 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 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() - ); + pressure_force_kernel_test_utils::global_dot(translationTrue, residualTrue, f.mesh->GetComm()); - const double dotProductScale = - std::fmax(residualNorm * translationNorm, 1.0); + const double dotProductScale = std::fmax(residualNorm * translationNorm, 1.0); CAPTURE(component, translationWork, dotProductScale); @@ -332,32 +452,27 @@ TEST_CASE( } TEST_CASE( - "Pressure Force Residual Respects byNODES Component Layout", - tags::barotrope &tags::pressure &tags::kernels &tags::integration - &tags::accuracy + "Pressure Force Residual Matches Independent Pressure Integral", + 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); + 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 mean_field::eos::Polytrope barotrope(3.0, 0.25); - const mfem::Vector enthalpyTrue = - pressure_force_kernel_test_utils::make_positive_asymmetric_enthalpy(f); + 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); + 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 + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue, residualTrue ); const int dimension = f.mesh->Dimension(); @@ -369,17 +484,21 @@ TEST_CASE( 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 - ); + 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() - ); + pressure_force_kernel_test_utils::global_dot(affineTestTrue, residualTrue, f.mesh->GetComm()); } } + const double pressureIntegral = pressure_force_kernel_test_utils::integrate_pressure( + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementTrue + ); + + REQUIRE(std::isfinite(pressureIntegral)); + + REQUIRE(pressureIntegral > 100.0 * std::numeric_limits::epsilon()); + double meanDiagonalWork = 0.0; for (int component = 0; component < dimension; ++component) { @@ -388,37 +507,173 @@ TEST_CASE( meanDiagonalWork /= static_cast(dimension); - // INFO( - // "Affine pressure virtual-work tensor:\n" - // << virtualWork - // ); + const double comparisonTolerance = 1.0e-6 * std::abs(pressureIntegral); + + INFO("Independent pressure integral = " << pressureIntegral); + + INFO("Expected diagonal virtual work = " << -pressureIntegral); INFO("Mean diagonal virtual work = " << meanDiagonalWork); - REQUIRE( - std::abs(meanDiagonalWork) > - 100.0 * std::numeric_limits::epsilon() - ); + INFO("Comparison tolerance = " << comparisonTolerance); - const double comparisonTolerance = 1.0e-8 * std::abs(meanDiagonalWork); + /* + * This separate mean check gives a compact diagnostic if all three + * diagonal components drift together. + */ + CHECK(std::abs(meanDiagonalWork + pressureIntegral) <= comparisonTolerance); 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 - ); + const double expectedWork = component == coordinate ? -pressureIntegral : 0.0; - if (component == coordinate) { - CHECK( - std::abs(computedWork - meanDiagonalWork) <= - comparisonTolerance - ); - } else { - CHECK(std::abs(computedWork) <= comparisonTolerance); - } + CAPTURE(component, coordinate, computedWork, expectedWork, pressureIntegral, comparisonTolerance); + + CHECK(std::abs(computedWork - expectedWork) <= comparisonTolerance); } } -} \ No newline at end of file + + const double relativeMeanError = std::abs(meanDiagonalWork + pressureIntegral) / std::abs(pressureIntegral); + + INFO("Relative mean diagonal error = " << relativeMeanError); + + CHECK(relativeMeanError <= 1.0e-6); +} + +TEST_CASE( + "Pressure Force Residual Matches Deformed Pressure Volume Variation", + 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()); + + const mean_field::eos::Polytrope barotrope(3.0, 0.25); + + /* + * This field is positive but spatially nonuniform, so the test + * exercises a genuinely nonuniform pressure distribution. + */ + const mfem::Vector enthalpyTrue = + pressure_force_kernel_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.37); + + /* + * make_displacement() contains anisotropic diagonal terms and + * quadratic cross terms. A scale of 0.67 therefore provides a + * nonzero, nonspherical, valid base geometry. + */ + const mfem::Vector baseDisplacementTrue = gravity_prepared_test_utils::make_displacement(f, 0.67); + + /* + * Differentiate along the same smooth deformation family. Thus + * + * d(epsilon) = (0.67 + epsilon) d_shape. + * + * This gives a controlled geometry path while still evaluating + * the derivative at a genuinely deformed base state. + */ + const mfem::Vector displacementVariationTrue = gravity_prepared_test_utils::make_displacement(f, 1.0); + + const double baseDisplacementNorm = + gravity_prepared_test_utils::global_norm(baseDisplacementTrue, f.mesh->GetComm()); + + const double variationNorm = gravity_prepared_test_utils::global_norm(displacementVariationTrue, f.mesh->GetComm()); + + REQUIRE(baseDisplacementNorm > 100.0 * std::numeric_limits::epsilon()); + + REQUIRE(variationNorm > 100.0 * std::numeric_limits::epsilon()); + + mfem::Vector residualTrue; + + mean_field::operators::kernels::apply_pressure_force_residual( + f, *f.domainMapperStateless, barotrope, enthalpyTrue, baseDisplacementTrue, residualTrue + ); + + REQUIRE(residualTrue.Size() == f.displacementFes->GetTrueVSize()); + + const double residualWork = + pressure_force_kernel_test_utils::global_dot(displacementVariationTrue, residualTrue, f.mesh->GetComm()); + + REQUIRE(std::isfinite(residualWork)); + + REQUIRE(std::abs(residualWork) > 100.0 * std::numeric_limits::epsilon()); + + /* + * The relatively broad initial sweep lets us see the expected + * centered-difference convergence before reaching the quadrature + * and representation plateau. + */ + constexpr std::array differenceSteps{1.0e-2, 5.0e-3, 2.5e-3, 1.25e-3}; + + double bestRelativeDiscrepancy = std::numeric_limits::infinity(); + + for (const double differenceStep : differenceSteps) { + mfem::Vector displacementPlus(baseDisplacementTrue); + + mfem::Vector displacementMinus(baseDisplacementTrue); + + displacementPlus.Add(differenceStep, displacementVariationTrue); + + displacementMinus.Add(-differenceStep, displacementVariationTrue); + + const double pressureIntegralPlus = pressure_force_kernel_test_utils::integrate_pressure( + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementPlus + ); + + const double pressureIntegralMinus = pressure_force_kernel_test_utils::integrate_pressure( + f, *f.domainMapperStateless, barotrope, enthalpyTrue, displacementMinus + ); + + REQUIRE(std::isfinite(pressureIntegralPlus)); + REQUIRE(std::isfinite(pressureIntegralMinus)); + + const double pressureVolumeDerivative = (pressureIntegralPlus - pressureIntegralMinus) / (2.0 * differenceStep); + + REQUIRE(std::isfinite(pressureVolumeDerivative)); + + double comparisonScale = std::abs(residualWork); + + if (std::abs(pressureVolumeDerivative) > comparisonScale) { + comparisonScale = std::abs(pressureVolumeDerivative); + } + + REQUIRE(comparisonScale > 100.0 * std::numeric_limits::epsilon()); + + const double absoluteDiscrepancy = std::abs(residualWork + pressureVolumeDerivative); + + const double relativeDiscrepancy = absoluteDiscrepancy / comparisonScale; + + if (relativeDiscrepancy < bestRelativeDiscrepancy) { + bestRelativeDiscrepancy = relativeDiscrepancy; + } + + INFO("Difference step = " << differenceStep); + + INFO("Pressure residual work = " << residualWork); + + INFO("Pressure-volume derivative = " << pressureVolumeDerivative); + + INFO("Residual work plus derivative = " << residualWork + pressureVolumeDerivative); + + INFO("Relative discrepancy = " << relativeDiscrepancy); + + /* + * The signs must be opposite because the implemented pressure + * force is the negative variation of the pressure-volume + * functional. + */ + CHECK(residualWork * pressureVolumeDerivative < 0.0); + } + + INFO("Best pressure-volume relative discrepancy = " << bestRelativeDiscrepancy); + + /* + * This is intentionally a provisional but meaningful threshold. + * We will tighten it after measuring the convergence plateau. + */ + CHECK(bestRelativeDiscrepancy < 1.0e-8); +} diff --git a/tests/operators/prepared_barotropic_closure.cpp b/tests/operators/prepared_barotropic_closure.cpp index c1ec5b5..4749617 100644 --- a/tests/operators/prepared_barotropic_closure.cpp +++ b/tests/operators/prepared_barotropic_closure.cpp @@ -1,93 +1,192 @@ #include #include #include +#include #include #include +#include #include +#include import mean_field; import test_helpers; -namespace { - mfem::Vector project_scalar( +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 field(&finiteElementSpace); - - field.ProjectCoefficient(coefficient); + mfem::ParGridFunction fieldValue(&finiteElementSpace); + fieldValue.ProjectCoefficient(coefficient); mfem::Vector trueVector; - field.GetTrueDofs(trueVector); + fieldValue.GetTrueDofs(trueVector); return trueVector; } - mfem::Vector make_base_density(const mean_field::fem::FEM &f) { + [[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 0.42 + 0.025 * position(0) - 0.012 * position(1) + 0.007 * position(2); }); - return project_scalar(*f.densityFes, coefficient); } - mfem::Vector make_base_enthalpy(const mean_field::fem::FEM &f) { + [[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 0.92 + 0.018 * position(0) - 0.011 * position(1) + 0.006 * position(2); }); - return project_scalar(*f.enthalpyFes, coefficient); } - mfem::Vector make_enthalpy_variation(const mean_field::fem::FEM &f) { + [[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); } - mfem::Vector make_combined_variation( - const mfem::Vector &densityVariation, - const mfem::Vector &enthalpyVariation + [[nodiscard]] mfem::Vector make_constant_field( + mfem::ParFiniteElementSpace &finiteElementSpace, + const double value ) { - mfem::Vector combinedVariation( - densityVariation.Size() + enthalpyVariation.Size() - ); - - for (int densityDof = 0; densityDof < densityVariation.Size(); - ++densityDof) { - combinedVariation(densityDof) = densityVariation(densityDof); - } - - for (int enthalpyDof = 0; enthalpyDof < enthalpyVariation.Size(); - ++enthalpyDof) { - combinedVariation(densityVariation.Size() + enthalpyDof) = - enthalpyVariation(enthalpyDof); - } - - return combinedVariation; + mfem::ConstantCoefficient coefficient(value); + return project_scalar(finiteElementSpace, coefficient); } - struct ClosureCondition { - const char *name; + [[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 barotrope on identity geometry", + {{.name = "Linear polytrope on identity geometry", .polytropicIndex = 1.0, .polytropicConstant = 0.8, .enthalpyOffset = 0.65, @@ -97,7 +196,7 @@ namespace { .densityGradient = 0.004, .deformationScale = 0.0, .directionPhase = 0.31}, - {.name = "Fractional barotrope on moderate deformation", + {.name = "Fractional polytrope on moderate deformation", .polytropicIndex = 1.5, .polytropicConstant = 1.2, .enthalpyOffset = 0.90, @@ -107,7 +206,7 @@ namespace { .densityGradient = 0.003, .deformationScale = 0.45, .directionPhase = 0.53}, - {.name = "Target n=3 barotrope on strong deformation", + {.name = "Target n=3 polytrope on strong deformation", .polytropicIndex = 3.0, .polytropicConstant = 1.5, .enthalpyOffset = 1.20, @@ -119,877 +218,634 @@ namespace { .directionPhase = 0.79}} }; - double evaluate_enthalpy( + [[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)); + condition.enthalpyGradient * (0.50 * position(0) - 0.30 * position(1) + 0.20 * position(2)); } - mfem::Vector project_scalar_field( - mfem::ParFiniteElementSpace &finiteElementSpace, - mfem::Coefficient &coefficient - ) { - mfem::ParGridFunction field(&finiteElementSpace); - - field.ProjectCoefficient(coefficient); - - mfem::Vector trueVector; - field.GetTrueDofs(trueVector); - - return trueVector; - } - - mfem::Vector make_enthalpy( + [[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_field(*f.enthalpyFes, coefficient); + mfem::FunctionCoefficient coefficient([condition](const mfem::Vector &position) { + return evaluate_enthalpy(position, condition); + }); + return project_scalar(*f.enthalpyFes, coefficient); } - mfem::Vector make_density( + [[nodiscard]] mfem::Vector make_density( const mean_field::fem::FEM &f, - const mean_field::physics::PolytropicBarotrope &barotrope, + const mean_field::eos::Polytrope &equationOfState, const ClosureCondition &condition ) { - mfem::FunctionCoefficient coefficient( - [&barotrope, condition](const mfem::Vector &position) { - const double enthalpy = evaluate_enthalpy(position, condition); - - return condition.densityFactor * - barotrope.density_from_enthalpy(enthalpy) + - condition.densityOffset + - condition.densityGradient * - (0.40 * position(0) + 0.25 * position(1) - - 0.15 * position(2)); - } - ); - - return project_scalar_field(*f.densityFes, coefficient); + mfem::FunctionCoefficient coefficient([&equationOfState, condition](const mfem::Vector &position) { + const double enthalpy = evaluate_enthalpy(position, condition); + return condition.densityFactor * equationOfState.density_from_enthalpy(enthalpy) + condition.densityOffset + + condition.densityGradient * (0.40 * position(0) + 0.25 * position(1) - 0.15 * position(2)); + }); + return project_scalar(*f.densityFes, coefficient); } - mfem::Vector make_constant_field( - mfem::ParFiniteElementSpace &finiteElementSpace, - const double value + TEST_CASE( + "Prepared Barotropic Closure Uses FieldDof Supported Dimensions", + tags::barotrope &tags::closure &tags::prepared &tags::field &tags::unit ) { - mfem::ConstantCoefficient coefficient(value); + using Operator = mean_field::operators::PreparedBarotropicClosureOperator; - return project_scalar_field(finiteElementSpace, coefficient); + 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); + + 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); } -} // namespace + 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()); -TEST_CASE( - "Prepared Barotropic Closure Matches Stateless Kernels", - tags::hydro &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); - - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); - - mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( - f, *f.domainMapperStateless, barotrope - ); - - REQUIRE_FALSE(preparedOperator.IsPrepared()); - REQUIRE(preparedOperator.Height() == f.densityFes->GetTrueVSize()); - REQUIRE( - preparedOperator.Width() == - f.densityFes->GetTrueVSize() + f.enthalpyFes->GetTrueVSize() - ); - REQUIRE(preparedOperator.GetDensitySize() == f.densityFes->GetTrueVSize()); - REQUIRE( - preparedOperator.GetEnthalpySize() == f.enthalpyFes->GetTrueVSize() - ); - - const mfem::Vector baseDensity = make_base_density(f); - - const mfem::Vector baseEnthalpy = make_base_enthalpy(f); - - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); - - const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.43 + 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 displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.63); - - const mfem::Vector enthalpyVariation = make_enthalpy_variation(f); - - mfem::Vector zeroDensity(f.densityFes->GetTrueVSize()); - zeroDensity = 0.0; - - mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize()); - zeroEnthalpy = 0.0; - - preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement); - - mfem::Vector preparedResidual; - mfem::Vector preparedDensityAction; - mfem::Vector preparedEnthalpyAction; - mfem::Vector preparedSplitAction; - mfem::Vector preparedCombinedAction; - - preparedOperator.BuildResidual(preparedResidual); - - preparedOperator.Mult( - densityVariation, zeroEnthalpy, displacementVariation, - preparedDensityAction - ); - - preparedOperator.Mult( - zeroDensity, enthalpyVariation, displacementVariation, - preparedEnthalpyAction - ); - - preparedOperator.Mult( - densityVariation, enthalpyVariation, displacementVariation, - preparedSplitAction - ); - - const mfem::Vector combinedVariation = - make_combined_variation(densityVariation, enthalpyVariation); - - preparedOperator.Mult(combinedVariation, preparedCombinedAction); - - mfem::Vector referenceResidual; - mfem::Vector referenceDensityAction; - mfem::Vector referenceEnthalpyAction; - mfem::Vector referenceDisplacementAction; - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - displacement, referenceResidual - ); - - mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, displacement, - referenceDensityAction - ); - - mean_field::operators::kernels::apply_barotropic_closure_enthalpy_action( - f, *f.domainMapperStateless, barotrope, baseEnthalpy, enthalpyVariation, - displacement, referenceEnthalpyAction - ); - - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, baseEnthalpy, - displacement, displacementVariation, referenceDisplacementAction - ); - - mfem::Vector referenceCombinedAction(referenceDensityAction); - referenceCombinedAction += referenceEnthalpyAction; - referenceCombinedAction += referenceDisplacementAction; - - const MPI_Comm communicator = f.mesh->GetComm(); - - const double residualError = gravity_prepared_test_utils::relative_error( - preparedResidual, referenceResidual, communicator - ); - - const double densityError = gravity_prepared_test_utils::relative_error( - preparedDensityAction, referenceDensityAction, communicator - ); - - const double enthalpyError = gravity_prepared_test_utils::relative_error( - preparedEnthalpyAction, referenceEnthalpyAction, communicator - ); - - const double splitError = gravity_prepared_test_utils::relative_error( - preparedSplitAction, referenceCombinedAction, communicator - ); - - const double combinedError = gravity_prepared_test_utils::relative_error( - preparedCombinedAction, referenceCombinedAction, communicator - ); - - INFO("Prepared residual error = " << residualError); - INFO("Prepared density-action error = " << densityError); - INFO("Prepared enthalpy-action error = " << enthalpyError); - INFO("Prepared split-action error = " << splitError); - INFO("Prepared combined-action error = " << combinedError); - - CHECK(preparedOperator.IsPrepared()); - CHECK(preparedOperator.GetPreparationCount() == 1); - CHECK(residualError < 2.0e-12); - CHECK(densityError < 2.0e-12); - CHECK(enthalpyError < 2.0e-12); - CHECK(splitError < 2.0e-12); - CHECK(combinedError < 2.0e-12); -} - -TEST_CASE( - "Prepared Barotropic Closure Jacobian Matches Centered Difference", - tags::hydro &tags::jacobian &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); - - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); - - mfem::Vector baseDensity = make_base_density(f); - - mfem::Vector baseEnthalpy = make_base_enthalpy(f); - - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); - - const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.71 - ); - - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.63); - - const mfem::Vector enthalpyVariation = make_enthalpy_variation(f); - - mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( - f, *f.domainMapperStateless, barotrope - ); - - preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement); - - mfem::Vector analyticAction; - - preparedOperator.Mult( - densityVariation, enthalpyVariation, displacementVariation, - analyticAction - ); - - constexpr double differenceStep = 1.0e-6; - - const mfem::Vector plusDensity = - gravity_prepared_test_utils::linear_combination( - baseDensity, 1.0, densityVariation, differenceStep - ); - - const mfem::Vector minusDensity = - gravity_prepared_test_utils::linear_combination( - baseDensity, 1.0, densityVariation, -differenceStep - ); - - const mfem::Vector plusEnthalpy = - gravity_prepared_test_utils::linear_combination( - baseEnthalpy, 1.0, enthalpyVariation, differenceStep - ); - - const mfem::Vector minusEnthalpy = - gravity_prepared_test_utils::linear_combination( - baseEnthalpy, 1.0, enthalpyVariation, -differenceStep - ); - - mfem::Vector plusResidual; - mfem::Vector minusResidual; - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, plusDensity, plusEnthalpy, - displacement, plusResidual - ); - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, minusDensity, minusEnthalpy, - displacement, minusResidual - ); - - mfem::Vector finiteDifference(plusResidual); - finiteDifference -= minusResidual; - finiteDifference *= 1.0 / (2.0 * differenceStep); - - const double relativeError = gravity_prepared_test_utils::relative_error( - analyticAction, finiteDifference, f.mesh->GetComm() - ); - - INFO("Prepared EOS centered-difference error = " << relativeError); - - CHECK(relativeError < 2.0e-8); -} - -TEST_CASE( - "Prepared Barotropic Closure Reuses Frozen Data", - tags::hydro &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); - - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); - - mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( - f, *f.domainMapperStateless, barotrope - ); - - CHECK_FALSE(preparedOperator.IsPrepared()); - CHECK(preparedOperator.GetPreparationCount() == 0); - - mfem::Vector baseDensity = make_base_density(f); - - mfem::Vector baseEnthalpy = make_base_enthalpy(f); - - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 1.0); - - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.63); - - const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.31 - ); - - const mfem::Vector enthalpyVariation = make_enthalpy_variation(f); - - preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement); - - REQUIRE(preparedOperator.IsPrepared()); - REQUIRE(preparedOperator.GetPreparationCount() == 1); - - mfem::Vector firstResidual; - mfem::Vector firstAction; - - preparedOperator.BuildResidual(firstResidual); - - preparedOperator.Mult( - densityVariation, enthalpyVariation, displacementVariation, firstAction - ); - - baseDensity = 7.0; - baseEnthalpy = 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(); - - const double residualReuseError = - gravity_prepared_test_utils::relative_error( - repeatedResidual, firstResidual, communicator - ); - - const double actionReuseError = gravity_prepared_test_utils::relative_error( - repeatedAction, firstAction, communicator - ); - - INFO("Frozen residual reuse error = " << residualReuseError); - INFO("Frozen action reuse error = " << actionReuseError); - - CHECK(residualReuseError < 2.0e-14); - CHECK(actionReuseError < 2.0e-14); - CHECK(preparedOperator.GetPreparationCount() == preparationCount); -} - -TEST_CASE( - "Prepared Barotropic Closure Reprepares For New Geometry", - tags::hydro &tags::mapping &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); - - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); - - const mfem::Vector baseDensity = make_base_density(f); - - const mfem::Vector baseEnthalpy = make_base_enthalpy(f); - - const mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.59 - ); - - mfem::Vector zeroEnthalpy(f.enthalpyFes->GetTrueVSize()); - zeroEnthalpy = 0.0; - - mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( - f, *f.domainMapperStateless, barotrope - ); - - mfem::Vector identityAction; - mfem::Vector deformedAction; - - for (const double deformationScale : {0.0, 1.0}) { - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, deformationScale); - - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.63); - - preparedOperator.Prepare(baseDensity, baseEnthalpy, displacement); + 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 preparedAction; - mfem::Vector referenceResidual; - mfem::Vector referenceAction; + 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); - preparedOperator.Mult( - densityVariation, zeroEnthalpy, displacementVariation, - preparedAction - ); + 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, barotrope, baseDensity, baseEnthalpy, - displacement, referenceResidual + 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 ); - mean_field::operators::kernels::apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, - displacement, referenceAction - ); + 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(); - const double residualError = - gravity_prepared_test_utils::relative_error( - preparedResidual, referenceResidual, communicator - ); + 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); - const double actionError = gravity_prepared_test_utils::relative_error( - preparedAction, referenceAction, communicator + 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 ); - INFO("Deformation scale = " << deformationScale); - INFO("Reprepared residual error = " << residualError); - INFO("Reprepared action error = " << actionError); + mfem::Vector finiteDifference = maps.density.gather(plusFullResidual); + mfem::Vector minusReduced = maps.density.gather(minusFullResidual); + finiteDifference -= minusReduced; + finiteDifference /= 2.0 * differenceStep; - CHECK(residualError < 2.0e-12); - CHECK(actionError < 2.0e-12); - - if (deformationScale == 0.0) { - identityAction = preparedAction; - } else { - deformedAction = preparedAction; - } + const double error = relative_error(analyticAction, finiteDifference, f.mesh->GetComm()); + INFO("Reduced closure centered-difference error = " << error); + CHECK(error < 2.0e-7); } - const double geometryChange = gravity_prepared_test_utils::relative_error( - deformedAction, identityAction, f.mesh->GetComm() - ); + 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()); - INFO("Prepared closure geometry change = " << geometryChange); + const Maps maps(f); + const mean_field::eos::Polytrope equationOfState(3.0, 1.5); + mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState + ); - CHECK(preparedOperator.GetPreparationCount() == 2); - CHECK(geometryChange > 1.0e-5); -} + 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)); -TEST_CASE( - "Complete Barotropic Closure Matches Blocks And Centered Differences " - "Across Conditions", - tags::barotrope &tags::closure &tags::hydro &tags::integration - &tags::jacobian &tags::mapping &tags::physics &tags::prepared -) { - auto args = test_utils::setup_args(); + 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)); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), make_dependencies()); + REQUIRE(preparedOperator.GetPreparationCount() == 1); - REQUIRE(f.domainMapperStateless != nullptr); + mfem::Vector firstResidual; + mfem::Vector firstAction; + preparedOperator.BuildResidual(firstResidual); + preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, firstAction); - const MPI_Comm communicator = f.mesh->GetComm(); + density = 7.0; + enthalpy = 3.0; + displacement *= -4.0; - constexpr double differenceStep = 1.0e-5; + const std::uint64_t preparationCount = preparedOperator.GetPreparationCount(); - for (std::size_t conditionIndex = 0; conditionIndex < + mfem::Vector repeatedResidual; + mfem::Vector repeatedAction; + preparedOperator.BuildResidual(repeatedResidual); + preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, repeatedAction); - conditions.size(); - ++conditionIndex) { - const auto &condition = + 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); + } - conditions[conditionIndex]; + 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()); - DYNAMIC_SECTION(condition.name) { - const mean_field::physics::PolytropicBarotrope barotrope( - condition.polytropicIndex, condition.polytropicConstant - ); + const Maps maps(f); + const mean_field::eos::Polytrope equationOfState(3.0, 1.5); - const mfem::Vector baseDensity = + 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) + ); - make_density(f, barotrope, condition); + mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size()); + mfem::Vector zeroDisplacementVariation(maps.displacement.reduced_size()); + zeroEnthalpy = 0.0; + zeroDisplacementVariation = 0.0; - const mfem::Vector baseEnthalpy = + mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState + ); - make_enthalpy(f, condition); + mfem::Vector identityAction; + mfem::Vector deformedAction; + ClosureDependencies dependencies = make_dependencies(); - const mfem::Vector baseDisplacement = - gravity_prepared_test_utils::make_displacement( - f, condition.deformationScale - ); + for (const double deformationScale : {0.0, 1.0}) { + CAPTURE(deformationScale); - mfem::Vector densityVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.densityFes->GetTrueVSize(), condition.directionPhase - ); + const mfem::Vector displacement = + reduce(maps.displacement, gravity_prepared_test_utils::make_displacement(f, deformationScale)); - mfem::Vector enthalpyVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), - condition.directionPhase + 0.27 - ); - - mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement( - f, condition.directionPhase - ); - - mfem::Vector densityAction; - mfem::Vector enthalpyAction; - mfem::Vector displacementAction; - - mean_field::operators::kernels:: - apply_barotropic_closure_density_action( - f, *f.domainMapperStateless, barotrope, densityVariation, - baseDisplacement, densityAction - ); - - mean_field::operators::kernels:: - apply_barotropic_closure_enthalpy_action( - f, *f.domainMapperStateless, barotrope, baseEnthalpy, - enthalpyVariation, baseDisplacement, enthalpyAction - ); - - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, baseDensity, - baseEnthalpy, baseDisplacement, displacementVariation, - displacementAction - ); - - double densityActionNorm = gravity_prepared_test_utils::global_norm( - densityAction, communicator - ); - - double enthalpyActionNorm = - gravity_prepared_test_utils::global_norm( - enthalpyAction, communicator - ); - - double displacementActionNorm = - gravity_prepared_test_utils::global_norm( - displacementAction, communicator - ); - - REQUIRE(densityActionNorm > 1.0e-12); - REQUIRE(enthalpyActionNorm > 1.0e-12); - REQUIRE(displacementActionNorm > 1.0e-12); - - const double targetActionNorm = std::min( - {densityActionNorm, enthalpyActionNorm, displacementActionNorm} - ); - - const double densityScale = targetActionNorm / densityActionNorm; - - const double enthalpyScale = targetActionNorm / enthalpyActionNorm; - - const double displacementScale = - targetActionNorm / displacementActionNorm; - - densityVariation *= densityScale; - densityAction *= densityScale; - - enthalpyVariation *= enthalpyScale; - enthalpyAction *= enthalpyScale; - - displacementVariation *= displacementScale; - displacementAction *= displacementScale; - - densityActionNorm = gravity_prepared_test_utils::global_norm( - densityAction, communicator - ); - - enthalpyActionNorm = gravity_prepared_test_utils::global_norm( - enthalpyAction, communicator - ); - - displacementActionNorm = gravity_prepared_test_utils::global_norm( - displacementAction, communicator - ); - - mean_field::operators::context::barotropic:: - BarotropicClosureLinearizationContext context( - f, *f.domainMapperStateless, barotrope - ); - - const std::uint64_t revisionBase = - 100 + static_cast(10 * conditionIndex); - - const mean_field::operators::context::barotropic:: - BarotropicClosureRevisions revisions{ - .density = revisionBase + 1, - .enthalpy = revisionBase + 2, - .displacement = revisionBase + 3 - }; - - context.Prepare( - baseDensity, baseEnthalpy, baseDisplacement, revisions - ); + preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies); mfem::Vector preparedResidual; - mfem::Vector referenceResidual; - - context.BuildResidual(preparedResidual); - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, baseDensity, - baseEnthalpy, baseDisplacement, referenceResidual - ); - - const double residualEvaluationError = - gravity_prepared_test_utils::relative_error( - preparedResidual, referenceResidual, communicator - ); - mfem::Vector preparedAction; + preparedOperator.BuildResidual(preparedResidual); + preparedOperator.Mult(densityVariation, zeroEnthalpy, zeroDisplacementVariation, preparedAction); - context.GetOperator().Mult( - densityVariation, enthalpyVariation, displacementVariation, - preparedAction - ); - - mfem::Vector blockSum(densityAction); - - blockSum += enthalpyAction; - blockSum += displacementAction; - - const double blockAssemblyError = - gravity_prepared_test_utils::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 plusResidual; - mfem::Vector minusResidual; - + mfem::Vector fullReferenceResidual; + mfem::Vector fullReferenceAction; mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, plusDensity, - plusEnthalpy, plusDisplacement, plusResidual + 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 ); - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, minusDensity, - minusEnthalpy, minusDisplacement, minusResidual - ); + const mfem::Vector referenceResidual = maps.density.gather(fullReferenceResidual); + const mfem::Vector referenceAction = maps.density.gather(fullReferenceAction); - mfem::Vector finiteDifference(plusResidual); + 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); - finiteDifference -= minusResidual; + if (deformationScale == 0.0) { + identityAction = preparedAction; + } else { + deformedAction = preparedAction; + } - finiteDifference *= 1.0 / (2.0 * differenceStep); + ++dependencies.displacement.revision; + } - mfem::Vector finiteDifferenceError(preparedAction); + 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); + } - finiteDifferenceError -= finiteDifference; + 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 double finiteDifferenceErrorNorm = - gravity_prepared_test_utils::global_norm( - finiteDifferenceError, communicator + 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 double blockNormSum = - densityActionNorm + enthalpyActionNorm + displacementActionNorm; + 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 double blockScaledDifferenceError = - finiteDifferenceErrorNorm / blockNormSum; + const mfem::Vector baseDensity = reduce(maps.density, fullBaseDensityRaw); + const mfem::Vector baseEnthalpy = reduce(maps.enthalpy, fullBaseEnthalpyRaw); + const mfem::Vector baseDisplacement = reduce(maps.displacement, fullBaseDisplacementRaw); - const double completeRelativeError = - gravity_prepared_test_utils::relative_error( - preparedAction, finiteDifference, communicator + 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) ); - INFO("Condition = " << condition.name); + mfem::Vector fullDensityAction; + mfem::Vector fullEnthalpyAction; + mfem::Vector fullDisplacementAction; - INFO("Polytropic index = " << condition.polytropicIndex); + 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 + ); - INFO("Deformation scale = " << condition.deformationScale); + mfem::Vector densityAction = maps.density.gather(fullDensityAction); + mfem::Vector enthalpyAction = maps.density.gather(fullEnthalpyAction); + mfem::Vector displacementAction = maps.density.gather(fullDisplacementAction); - INFO("Prepared residual error = " << residualEvaluationError); + double densityNorm = global_norm(densityAction, communicator); + double enthalpyNorm = global_norm(enthalpyAction, communicator); + double displacementNorm = global_norm(displacementAction, communicator); - INFO("Complete block-assembly error = " << blockAssemblyError); + REQUIRE(densityNorm > 1.0e-12); + REQUIRE(enthalpyNorm > 1.0e-12); + REQUIRE(displacementNorm > 1.0e-12); - INFO("Density-action norm = " << densityActionNorm); + const double targetNorm = std::min({densityNorm, enthalpyNorm, displacementNorm}); + const double densityScale = targetNorm / densityNorm; + const double enthalpyScale = targetNorm / enthalpyNorm; + const double displacementScale = targetNorm / displacementNorm; - INFO("Enthalpy-action norm = " << enthalpyActionNorm); + densityVariation *= densityScale; + enthalpyVariation *= enthalpyScale; + displacementVariation *= displacementScale; + densityAction *= densityScale; + enthalpyAction *= enthalpyScale; + displacementAction *= displacementScale; - INFO("Displacement-action norm = " << displacementActionNorm); + densityNorm = global_norm(densityAction, communicator); + enthalpyNorm = global_norm(enthalpyAction, communicator); + displacementNorm = global_norm(displacementAction, communicator); - INFO( - "Complete centered-difference relative error = " - << completeRelativeError - ); + mean_field::operators::PreparedBarotropicClosureOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState + ); + preparedOperator.Prepare( + make_state_view(baseDensity, baseEnthalpy, baseDisplacement), make_dependencies() + ); - INFO( - "Block-scaled centered-difference error = " - << blockScaledDifferenceError - ); + mfem::Vector preparedResidual; + mfem::Vector preparedAction; + preparedOperator.BuildResidual(preparedResidual); + preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedAction); - CHECK(context.GetPreparationCount() == 1); - CHECK(residualEvaluationError < 5.0e-12); - CHECK(blockAssemblyError < 5.0e-12); - CHECK(blockScaledDifferenceError < 2.0e-7); + 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 Barotropic Closure Remains Zero Under Deformation", - tags::barotrope &tags::closure &tags::hydro &tags::integration - &tags::jacobian &tags::mapping &tags::physics -) { - auto args = test_utils::setup_args(); + 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()); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + const Maps maps(f); + const mean_field::eos::Polytrope equationOfState(3.0, 1.5); - REQUIRE(f.domainMapperStateless != nullptr); + constexpr double enthalpyValue = 1.20; + const double equilibriumDensityValue = equationOfState.density_from_enthalpy(enthalpyValue); - const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.5); + 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)); - constexpr double enthalpyValue = 1.20; + mfem::Vector zeroDensity(maps.density.reduced_size()); + mfem::Vector zeroEnthalpy(maps.enthalpy.reduced_size()); + zeroDensity = 0.0; + zeroEnthalpy = 0.0; - const double equilibriumDensityValue = - barotrope.density_from_enthalpy(enthalpyValue); + const MPI_Comm communicator = f.mesh->GetComm(); - const mfem::Vector enthalpy = - make_constant_field(*f.enthalpyFes, enthalpyValue); + 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)); - const mfem::Vector equilibriumDensity = - make_constant_field(*f.densityFes, equilibriumDensityValue); - - const mfem::Vector referenceDensity = - make_constant_field(*f.densityFes, equilibriumDensityValue + 1.0); - - const mfem::Vector displacementVariation = - gravity_prepared_test_utils::make_displacement(f, 0.67); - - 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 = - 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 ); - mfem::Vector exactResidual; - mfem::Vector referenceResidual; - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, equilibriumDensity, - enthalpy, displacement, exactResidual - ); - - mean_field::operators::kernels::apply_barotropic_closure( - f, *f.domainMapperStateless, barotrope, referenceDensity, - enthalpy, displacement, referenceResidual - ); - - mfem::Vector exactGeometryAction; - mfem::Vector referenceGeometryAction; - - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, equilibriumDensity, - enthalpy, displacement, displacementVariation, - exactGeometryAction + exactOperator.Prepare(make_state_view(equilibriumDensity, enthalpy, displacement), make_dependencies()); + referenceOperator.Prepare( + make_state_view(referenceDensity, enthalpy, displacement), make_dependencies() ); - mean_field::operators::kernels:: - apply_barotropic_closure_displacement_action( - f, *f.domainMapperStateless, barotrope, referenceDensity, - enthalpy, displacement, displacementVariation, - referenceGeometryAction - ); + mfem::Vector exactResidual; + mfem::Vector referenceResidual; + mfem::Vector exactGeometryAction; + mfem::Vector referenceGeometryAction; - const double exactResidualNorm = - gravity_prepared_test_utils::global_norm( - exactResidual, communicator - ); + exactOperator.BuildResidual(exactResidual); + referenceOperator.BuildResidual(referenceResidual); + exactOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, exactGeometryAction); + referenceOperator.Mult(zeroDensity, zeroEnthalpy, displacementVariation, referenceGeometryAction); - const double referenceResidualNorm = - gravity_prepared_test_utils::global_norm( - referenceResidual, communicator - ); + 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); - const double exactGeometryNorm = - gravity_prepared_test_utils::global_norm( - exactGeometryAction, 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); - const double referenceGeometryNorm = - gravity_prepared_test_utils::global_norm( - referenceGeometryAction, communicator - ); - - INFO("Deformation scale = " << deformationScale); - - INFO("Exact-closure residual norm = " << exactResidualNorm); - - INFO("Reference residual norm = " << referenceResidualNorm); - - INFO("Exact-closure geometry-action norm = " << exactGeometryNorm); - - INFO("Reference 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); + REQUIRE(referenceResidualNorm > 1.0e-12); + REQUIRE(referenceGeometryNorm > 1.0e-14); + CHECK(exactResidualNorm <= 5.0e-12 * referenceResidualNorm); + CHECK(exactGeometryNorm <= 5.0e-12 * referenceGeometryNorm); + } } } -} \ No newline at end of file + +} // namespace prepared_barotropic_closure_test_utils diff --git a/tests/operators/prepared_displacement_operator.cpp b/tests/operators/prepared_displacement_operator.cpp new file mode 100644 index 0000000..3ddccb3 --- /dev/null +++ b/tests/operators/prepared_displacement_operator.cpp @@ -0,0 +1,954 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +import mean_field; +import test_helpers; + +namespace prepared_displacement_residual_test_utils { + using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form; + + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto gravityGradientValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); + + constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.gradient_term + ); + + constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.poisson_term + ); + + constexpr auto densityResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + [[nodiscard]] mean_field::field::FieldDofMap make_enthalpy_map(const mean_field::fem::FEM &f) { + return mean_field::field::make_field_dof_map(*f.enthalpyFes); + } + + [[nodiscard]] mean_field::operators::DisplacementResidualLayout make_layout(const mean_field::fem::FEM &f) { + const auto enthalpyMap = make_enthalpy_map(f); + + /* + * Transitional displacement-composer layout. + * + * Pressure has now migrated its h column to supported FieldDof + * coordinates, while the density-consuming mechanical children are + * intentionally still full-space until the next migration slice. + * + * The adapter only writes R_d. The unrelated residual-row sizes remain + * at their current full-space values in this standalone adapter test. + */ + const std::array valueSizes{ + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize(), enthalpyMap.reduced_size(), 1 + }; + + const std::array residualSizes{ + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(), + f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + return {valueSizes, residualSizes}; + } + + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.84 + 0.06 * std::sin(0.73 * position(0) + phase) + 0.04 * std::cos(0.61 * position(1) - phase) + + 0.025 * position(2) * position(2); + }); + + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } + + [[nodiscard]] mfem::Vector make_density_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.17 * std::sin(0.91 * position(0) + phase) - 0.11 * std::cos(0.79 * position(1) - phase) + + 0.07 * position(2); + }); + + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } + + [[nodiscard]] mfem::Vector make_gravity_gradient( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityFluxFes.get()); + + auto function = [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + + value(0) = 0.31 + 0.08 * position(0) + 0.03 * phase * position(1); + + value(1) = -0.17 + 0.06 * position(1) - 0.02 * phase * position(2); + + value(2) = 0.23 - 0.05 * position(2) + 0.025 * phase * position(0); + }; + + mfem::VectorFunctionCoefficient coefficient(3, function); + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } + + [[nodiscard]] mfem::Vector make_gravity_gradient_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityFluxFes.get()); + + auto function = [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + + value(0) = 0.14 * std::sin(position(0) + phase) + 0.03 * position(1); + + value(1) = -0.11 * std::cos(position(1) - phase) + 0.04 * position(2); + + value(2) = 0.09 * std::sin(position(2) + 0.5 * phase) - 0.02 * position(0); + }; + + mfem::VectorFunctionCoefficient coefficient(3, function); + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } + + [[nodiscard]] mfem::Vector make_positive_enthalpy( + const mean_field::fem::FEM &f, + const double phase + ) { + /* + * Build a full H1 test state first, then return exactly the + * solver-facing supported FieldDof coordinates consumed by the + * migrated pressure-force operator. + * + * Keeping the public helper name unchanged means every existing + * displacement-composer test automatically migrates to the new + * pressure contract without inventing a parallel "*_full" helper. + */ + mfem::Vector fullEnthalpy(f.enthalpyFes->GetTrueVSize()); + + for (int index = 0; index < fullEnthalpy.Size(); ++index) { + const double coordinate = static_cast(index + 1); + + fullEnthalpy(index) = + 0.93 + 0.09 * std::sin(0.23 * coordinate + phase) + 0.04 * std::cos(0.17 * coordinate - 0.5 * phase); + } + + return make_enthalpy_map(f).gather(fullEnthalpy); + } + + [[nodiscard]] mfem::Vector make_enthalpy_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::Vector fullDirection(f.enthalpyFes->GetTrueVSize()); + + for (int index = 0; index < fullDirection.Size(); ++index) { + const double coordinate = static_cast(index + 1); + + fullDirection(index) = + 0.27 * std::sin(0.19 * coordinate + phase) + 0.14 * std::cos(0.13 * coordinate - 0.5 * phase); + } + + return make_enthalpy_map(f).gather(fullDirection); + } + + [[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) { + mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91); + + const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27); + + direction -= second; + return direction; + } + + [[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) { + mfem::Vector angularVelocity(3); + angularVelocity(0) = scale * 0.17; + angularVelocity(1) = scale * -0.09; + angularVelocity(2) = scale * 0.62; + + mfem::Vector center(3); + center(0) = 0.04; + center(1) = -0.03; + center(2) = 0.02; + + return mean_field::physics::RigidRotation(angularVelocity, center); + } + + [[nodiscard]] mean_field::operators::DisplacementResidualDependencies make_dependencies() { + return { + .discretization = {.identity = 401, .revision = 3}, + .density = {.identity = 409, .revision = 5}, + .displacement = {.identity = 419, .revision = 7}, + .gravityGradient = {.identity = 421, .revision = 11}, + .enthalpy = {.identity = 431, .revision = 13}, + .rotation = {.identity = 433, .revision = 17} + }; + } + + [[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_gravity_revisions( + const mean_field::operators::DisplacementResidualDependencies &dependencies, + const std::uint64_t potentialRevision + ) { + return { + .discretization = {.value = dependencies.discretization.revision}, + .displacement = {.value = dependencies.displacement.revision}, + .density = {.value = dependencies.density.revision}, + .gravity_gradient = {.value = dependencies.gravityGradient.revision}, + .gravity_potential = {.value = potentialRevision} + }; + } + + void prepare_gravity_context( + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context, + const mfem::Vector &density, + const mfem::Vector &displacement, + const mfem::Vector &gravityGradient, + const mfem::Vector &gravityPotential, + const mean_field::operators::DisplacementResidualDependencies &dependencies, + const std::uint64_t potentialRevision + ) { + context.Prepare( + {.density = density, + .displacement = displacement, + .gravity_gradient = gravityGradient, + .gravity_potential = gravityPotential}, + make_gravity_revisions(dependencies, potentialRevision) + ); + } + + [[nodiscard]] double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const MPI_Comm communicator + ) { + REQUIRE(left.Size() == right.Size()); + + mfem::Vector difference(left); + difference -= right; + + const double scale = std::max( + {gravity_prepared_test_utils::global_norm(left, communicator), + gravity_prepared_test_utils::global_norm(right, communicator), + 100.0 * std::numeric_limits::epsilon()} + ); + + return gravity_prepared_test_utils::global_norm(difference, communicator) / scale; + } + + [[nodiscard]] mfem::Vector + explicit_residual_sum(const mean_field::operators::PreparedDisplacementResidualOperator &preparedOperator) { + mfem::Vector pressure; + mfem::Vector gravity; + mfem::Vector rotation; + + preparedOperator.GetPressureOperator().BuildResidual(pressure); + preparedOperator.GetGravityOperator().BuildResidual(gravity); + preparedOperator.GetRotationalOperator().BuildResidual(rotation); + + pressure += gravity; + pressure += rotation; + return pressure; + } + + template + [[nodiscard]] mfem::Vector copy_residual_block( + const mfem::Vector &action, + const mean_field::operators::DisplacementResidualLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + mfem::Vector result(layout.size(block)); + const int offset = layout.offset(block); + + for (int entry = 0; entry < result.Size(); ++entry) { + result(entry) = action(offset + entry); + } + + return result; + } +} // namespace prepared_displacement_residual_test_utils + +TEST_CASE( + "Prepared Displacement Residual Equals The Three Prepared Contributors", + tags::barotrope &tags::prepared &tags::integration &tags::residuals +) { + using Operator = mean_field::operators::PreparedDisplacementResidualOperator; + + 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); + + 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 auto enthalpyMap = prepared_displacement_residual_test_utils::make_enthalpy_map(f); + + const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.31); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.67); + + const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.47); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.53); + + REQUIRE(enthalpyMap.reduced_size() < enthalpyMap.full_size()); + REQUIRE(enthalpy.Size() == enthalpyMap.reduced_size()); + + const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19 + ); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.83); + + Operator preparedOperator(f, *f.domainMapperStateless, equationOfState, gravityContext); + + REQUIRE(preparedOperator.GetPressureOperator().GetEnthalpySize() == enthalpy.Size()); + + const auto initialReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + REQUIRE(initialReport.pressure.DidAnyWork()); + REQUIRE(initialReport.gravity.DidAnyWork()); + REQUIRE(initialReport.rotation.DidAnyWork()); + REQUIRE(initialReport.assembledResidual); + REQUIRE(preparedOperator.IsPrepared()); + + CHECK(&preparedOperator.GetFEM() == &f); + CHECK(&preparedOperator.GetGravityContext() == &gravityContext); + CHECK(&preparedOperator.GetGravityOperator().GetGravityContext() == &gravityContext); + + mfem::Vector compositeResidual; + preparedOperator.BuildResidual(compositeResidual); + + const mfem::Vector explicitResidual = + prepared_displacement_residual_test_utils::explicit_residual_sum(preparedOperator); + + const double compositionError = prepared_displacement_residual_test_utils::relative_difference( + compositeResidual, explicitResidual, f.mesh->GetComm() + ); + + INFO("Prepared residual composition error = " << compositionError); + CHECK(compositionError < 2.0e-15); + + const std::uint64_t preparationCount = preparedOperator.GetResidualPreparationCount(); + + const auto repeatedReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK_FALSE(repeatedReport.DidAnyWork()); + CHECK_FALSE(repeatedReport.assembledResidual); + CHECK(preparedOperator.GetResidualPreparationCount() == preparationCount); + CHECK(preparedOperator.IsPrepared()); +} + +TEST_CASE( + "Prepared Displacement Residual Selectively Orchestrates Its Children", + tags::barotrope &tags::prepared &tags::contexts &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()); + + mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.29); + + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61); + + mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.43); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.51); + + auto dependencies = prepared_displacement_residual_test_utils::make_dependencies(); + + std::uint64_t potentialRevision = 19; + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision + ); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.79); + + mean_field::operators::PreparedDisplacementResidualOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState, gravityContext + ); + + preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + gravityPotential = 0.17; + ++potentialRevision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision + ); + + const auto potentialReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK_FALSE(potentialReport.DidAnyWork()); + CHECK_FALSE(potentialReport.assembledResidual); + + enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.83); + ++dependencies.enthalpy.revision; + + const auto enthalpyReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK(enthalpyReport.pressure.DidAnyWork()); + CHECK_FALSE(enthalpyReport.gravity.DidAnyWork()); + CHECK_FALSE(enthalpyReport.rotation.DidAnyWork()); + CHECK(enthalpyReport.assembledResidual); + + gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.91); + ++dependencies.gravityGradient.revision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision + ); + + const auto gravityReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK_FALSE(gravityReport.pressure.DidAnyWork()); + CHECK(gravityReport.gravity.DidAnyWork()); + CHECK_FALSE(gravityReport.rotation.DidAnyWork()); + CHECK(gravityReport.assembledResidual); + + density = prepared_displacement_residual_test_utils::make_density(f, 1.07); + ++dependencies.density.revision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision + ); + + const auto densityReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK_FALSE(densityReport.pressure.DidAnyWork()); + CHECK(densityReport.gravity.DidAnyWork()); + CHECK(densityReport.rotation.DidAnyWork()); + CHECK(densityReport.assembledResidual); + + rotation = prepared_displacement_residual_test_utils::make_rotation(1.13); + ++dependencies.rotation.revision; + + const auto rotationReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK_FALSE(rotationReport.pressure.DidAnyWork()); + CHECK_FALSE(rotationReport.gravity.DidAnyWork()); + CHECK(rotationReport.rotation.DidAnyWork()); + CHECK(rotationReport.assembledResidual); + + displacement = gravity_prepared_test_utils::make_displacement(f, 0.89); + ++dependencies.displacement.revision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, potentialRevision + ); + + const auto displacementReport = preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + CHECK(displacementReport.pressure.DidAnyWork()); + CHECK(displacementReport.gravity.DidAnyWork()); + CHECK(displacementReport.rotation.DidAnyWork()); + CHECK(displacementReport.assembledResidual); +} + +TEST_CASE( + "Prepared Displacement Residual Jacobian Equals The Contributor Sums", + tags::barotrope &tags::prepared &tags::jacobian &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()); + + const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.37); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73); + + const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.59); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.61); + + const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.71); + + const mfem::Vector displacementDirection = + prepared_displacement_residual_test_utils::make_displacement_direction(f); + + const mfem::Vector gravityDirection = + prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.83); + + const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 0.97); + + const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19 + ); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.91); + + mean_field::operators::PreparedDisplacementResidualOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState, gravityContext + ); + + preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + mfem::Vector densityAction; + mfem::Vector displacementAction; + mfem::Vector gravityAction; + mfem::Vector enthalpyAction; + mfem::Vector completeAction; + + preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction); + + preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction); + + preparedOperator.ApplyGravityGradientJacobianAction(gravityDirection, gravityAction); + + preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, enthalpyAction); + + preparedOperator.ApplyCompleteJacobianAction( + densityDirection, displacementDirection, gravityDirection, enthalpyDirection, completeAction + ); + + mfem::Vector expectedDensity; + mfem::Vector expectedRotationDensity; + + preparedOperator.GetGravityOperator().ApplyDensityJacobianAction(densityDirection, expectedDensity); + + preparedOperator.GetRotationalOperator().ApplyDensityJacobianAction(densityDirection, expectedRotationDensity); + + expectedDensity += expectedRotationDensity; + + mfem::Vector expectedDisplacement; + mfem::Vector expectedGravityDisplacement; + mfem::Vector expectedRotationDisplacement; + + preparedOperator.GetPressureOperator().ApplyDisplacementJacobianAction(displacementDirection, expectedDisplacement); + + preparedOperator.GetGravityOperator().ApplyDisplacementJacobianAction( + displacementDirection, expectedGravityDisplacement + ); + + preparedOperator.GetRotationalOperator().ApplyDisplacementJacobianAction( + displacementDirection, expectedRotationDisplacement + ); + + expectedDisplacement += expectedGravityDisplacement; + expectedDisplacement += expectedRotationDisplacement; + + mfem::Vector expectedGravity; + preparedOperator.GetGravityOperator().ApplyGravityGradientJacobianAction(gravityDirection, expectedGravity); + + mfem::Vector expectedEnthalpy; + preparedOperator.GetPressureOperator().ApplyEnthalpyJacobianAction(enthalpyDirection, expectedEnthalpy); + + mfem::Vector summedColumns(densityAction); + summedColumns += displacementAction; + summedColumns += gravityAction; + summedColumns += enthalpyAction; + + const MPI_Comm communicator = f.mesh->GetComm(); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference(densityAction, expectedDensity, communicator) < + 2.0e-15 + ); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference( + displacementAction, expectedDisplacement, communicator + ) < 2.0e-15 + ); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference(gravityAction, expectedGravity, communicator) < + 2.0e-15 + ); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference(enthalpyAction, expectedEnthalpy, communicator) < + 2.0e-15 + ); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference(completeAction, summedColumns, communicator) < + 2.0e-15 + ); +} + +TEST_CASE( + "Prepared Displacement Residual Jacobian Matches A Simultaneous " + "Centered Difference On Deformed Geometry", + tags::barotrope &tags::prepared &tags::jacobian &tags::accuracy &tags::geometry +) { + 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 mfem::Vector baseDensity = prepared_displacement_residual_test_utils::make_density(f, 0.41); + + const mfem::Vector baseDisplacement = gravity_prepared_test_utils::make_displacement(f, 0.79); + + const mfem::Vector baseGravity = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.63); + + const mfem::Vector baseEnthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.67); + + const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.73); + + const mfem::Vector displacementDirection = + prepared_displacement_residual_test_utils::make_displacement_direction(f); + + const mfem::Vector gravityDirection = + prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.89); + + const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 1.01); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + auto dependencies = prepared_displacement_residual_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, baseDensity, baseDisplacement, baseGravity, gravityPotential, dependencies, 19 + ); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.87); + + mean_field::operators::PreparedDisplacementResidualOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState, gravityContext + ); + + preparedOperator.Prepare({.enthalpy = baseEnthalpy}, dependencies, rotation); + + mfem::Vector jacobianAction; + + preparedOperator.ApplyCompleteJacobianAction( + densityDirection, displacementDirection, gravityDirection, enthalpyDirection, jacobianAction + ); + + constexpr double step = 1.0e-5; + + mfem::Vector plusDensity(baseDensity); + plusDensity.Add(step, densityDirection); + + mfem::Vector minusDensity(baseDensity); + minusDensity.Add(-step, densityDirection); + + mfem::Vector plusDisplacement(baseDisplacement); + plusDisplacement.Add(step, displacementDirection); + + mfem::Vector minusDisplacement(baseDisplacement); + minusDisplacement.Add(-step, displacementDirection); + + mfem::Vector plusGravity(baseGravity); + plusGravity.Add(step, gravityDirection); + + mfem::Vector minusGravity(baseGravity); + minusGravity.Add(-step, gravityDirection); + + mfem::Vector plusEnthalpy(baseEnthalpy); + plusEnthalpy.Add(step, enthalpyDirection); + + mfem::Vector minusEnthalpy(baseEnthalpy); + minusEnthalpy.Add(-step, enthalpyDirection); + + ++dependencies.density.revision; + ++dependencies.displacement.revision; + ++dependencies.gravityGradient.revision; + ++dependencies.enthalpy.revision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, plusDensity, plusDisplacement, plusGravity, gravityPotential, dependencies, 19 + ); + + preparedOperator.Prepare({.enthalpy = plusEnthalpy}, dependencies, rotation); + + mfem::Vector plusResidual; + preparedOperator.BuildResidual(plusResidual); + + ++dependencies.density.revision; + ++dependencies.displacement.revision; + ++dependencies.gravityGradient.revision; + ++dependencies.enthalpy.revision; + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, minusDensity, minusDisplacement, minusGravity, gravityPotential, dependencies, 19 + ); + + preparedOperator.Prepare({.enthalpy = minusEnthalpy}, dependencies, rotation); + + mfem::Vector minusResidual; + preparedOperator.BuildResidual(minusResidual); + + plusResidual -= minusResidual; + plusResidual /= 2.0 * step; + + const double centeredDifferenceError = + prepared_displacement_residual_test_utils::relative_difference(jacobianAction, plusResidual, f.mesh->GetComm()); + + INFO("Composite simultaneous centered-difference error = " << centeredDifferenceError); + + CHECK(centeredDifferenceError < 8.0e-8); +} + +TEST_CASE( + "Prepared Displacement Residual MFEM Adapter Routes Only R-d", + tags::barotrope &tags::prepared &tags::jacobian &tags::mfem_operators &tags::unit +) { + using JacobianForm = mean_field::utils::blocks::barotropic_equilibrium_jacobian_form; + + using DisplacementResidualType = mean_field::utils::blocks::displacement::geometry::residual; + + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::density::mass::value, JacobianForm> + ); + + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::displacement::geometry::value, JacobianForm> + ); + + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::gravity::gradient::value, JacobianForm> + ); + + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::enthalpy::specific::value, JacobianForm> + ); + + STATIC_REQUIRE_FALSE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::gravity::poisson::value, JacobianForm> + ); + + STATIC_REQUIRE_FALSE( + mean_field::utils::blocks::has_jacobian_coupling_v< + DisplacementResidualType, mean_field::utils::blocks::barotropic_constant::mass_normalization::value, + JacobianForm> + ); + + 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 mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.43); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.71); + + const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.57); + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + const mfem::Vector enthalpy = prepared_displacement_residual_test_utils::make_positive_enthalpy(f, 0.69); + + const mfem::Vector densityDirection = prepared_displacement_residual_test_utils::make_density_direction(f, 0.77); + + const mfem::Vector displacementDirection = + prepared_displacement_residual_test_utils::make_displacement_direction(f); + + const mfem::Vector gravityDirection = + prepared_displacement_residual_test_utils::make_gravity_gradient_direction(f, 0.93); + + const mfem::Vector enthalpyDirection = prepared_displacement_residual_test_utils::make_enthalpy_direction(f, 1.03); + + const auto dependencies = prepared_displacement_residual_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + prepared_displacement_residual_test_utils::prepare_gravity_context( + gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19 + ); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.95); + + mean_field::operators::PreparedDisplacementResidualOperator preparedOperator( + f, *f.domainMapperStateless, equationOfState, gravityContext + ); + + preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation); + + const mean_field::operators::DisplacementResidualLayout layout = + prepared_displacement_residual_test_utils::make_layout(f); + + mean_field::operators::PreparedDisplacementResidualJacobianOperator adapter(layout, preparedOperator); + + CHECK(adapter.Width() == layout.value_offsets().Last()); + CHECK(adapter.Height() == layout.residual_offsets().Last()); + CHECK( + layout.size(prepared_displacement_residual_test_utils::enthalpyValue) == + preparedOperator.GetPressureOperator().GetEnthalpySize() + ); + + mfem::BlockVector direction(layout.value_offsets()); + direction = 0.0; + + direction.GetBlock(prepared_displacement_residual_test_utils::densityValue) = densityDirection; + + direction.GetBlock(prepared_displacement_residual_test_utils::displacementValue) = displacementDirection; + + direction.GetBlock(prepared_displacement_residual_test_utils::gravityGradientValue) = gravityDirection; + + direction.GetBlock(prepared_displacement_residual_test_utils::enthalpyValue) = enthalpyDirection; + + direction.GetBlock(prepared_displacement_residual_test_utils::gravityPotentialValue) = 0.59; + + direction.GetBlock(prepared_displacement_residual_test_utils::barotropicConstantValue) = -0.73; + + mfem::Vector expectedDisplacementAction; + + preparedOperator.ApplyCompleteJacobianAction( + densityDirection, displacementDirection, gravityDirection, enthalpyDirection, expectedDisplacementAction + ); + + mfem::Vector action; + adapter.Mult(direction, action); + + const mfem::Vector routedDisplacement = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::displacementResidual + ); + + CHECK( + prepared_displacement_residual_test_utils::relative_difference( + routedDisplacement, expectedDisplacementAction, f.mesh->GetComm() + ) < 2.0e-15 + ); + + const mfem::Vector gravityGradientBlock = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::gravityGradientResidual + ); + + const mfem::Vector gravityPotentialBlock = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::gravityPotentialResidual + ); + + const mfem::Vector densityBlock = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::densityResidual + ); + + const mfem::Vector enthalpyBlock = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::enthalpyResidual + ); + + const mfem::Vector massBlock = prepared_displacement_residual_test_utils::copy_residual_block( + action, layout, prepared_displacement_residual_test_utils::massResidual + ); + + CHECK(gravity_prepared_test_utils::global_norm(gravityGradientBlock, f.mesh->GetComm()) == 0.0); + + CHECK(gravity_prepared_test_utils::global_norm(gravityPotentialBlock, f.mesh->GetComm()) == 0.0); + + CHECK(gravity_prepared_test_utils::global_norm(densityBlock, f.mesh->GetComm()) == 0.0); + + CHECK(gravity_prepared_test_utils::global_norm(enthalpyBlock, f.mesh->GetComm()) == 0.0); + + CHECK(gravity_prepared_test_utils::global_norm(massBlock, f.mesh->GetComm()) == 0.0); +} diff --git a/tests/operators/prepared_gravity_source.cpp b/tests/operators/prepared_gravity_source.cpp index fe31e90..2060d2f 100644 --- a/tests/operators/prepared_gravity_source.cpp +++ b/tests/operators/prepared_gravity_source.cpp @@ -16,25 +16,18 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - operators::PreparedMappedGravitySourceOperator prepared_operator( - f, *f.domainMapperStateless - ); + operators::PreparedMappedGravitySourceOperator prepared_operator(f, *f.domainMapperStateless); REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize()); - REQUIRE( - prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize() - ); + REQUIRE(prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()); - const mfem::Vector density = prepared_test::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.41 - ); + const mfem::Vector density = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.41); const MPI_Comm communicator = f.mesh->GetComm(); mfem::Vector identity_action; mfem::Vector deformed_action; for (const double deformation_scale : {0.0, 1.0}) { - const mfem::Vector displacement = - prepared_test::make_displacement(f, deformation_scale); + const mfem::Vector displacement = prepared_test::make_displacement(f, deformation_scale); prepared_operator.Prepare(displacement); @@ -42,23 +35,13 @@ TEST_CASE( mfem::Vector reference_action; prepared_operator.Mult(density, prepared_action); - operators::kernels::apply_mapped_source( - f, *f.domainMapperStateless, density, displacement, reference_action - ); + operators::kernels::apply_mapped_source(f, *f.domainMapperStateless, density, displacement, reference_action); - const double relative_error = prepared_test::relative_error( - prepared_action, reference_action, communicator - ); + const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator); INFO("Deformation scale = " << deformation_scale); - INFO( - "Prepared source norm = " - << prepared_test::global_norm(prepared_action, communicator) - ); - INFO( - "Reference source norm = " - << prepared_test::global_norm(reference_action, communicator) - ); + INFO("Prepared source norm = " << prepared_test::global_norm(prepared_action, communicator)); + INFO("Reference source norm = " << prepared_test::global_norm(reference_action, communicator)); INFO("Relative prepared-source error = " << relative_error); REQUIRE(prepared_operator.IsPrepared()); @@ -71,9 +54,7 @@ TEST_CASE( } } - const double geometry_change = prepared_test::relative_error( - deformed_action, identity_action, communicator - ); + const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator); INFO("Relative source change under deformation = " << geometry_change); @@ -88,28 +69,17 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - operators::PreparedMappedGravitySourceOperator prepared_operator( - f, *f.domainMapperStateless - ); + operators::PreparedMappedGravitySourceOperator prepared_operator(f, *f.domainMapperStateless); REQUIRE(prepared_operator.Width() == f.densityFes->GetTrueVSize()); - REQUIRE( - prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize() - ); + REQUIRE(prepared_operator.Height() == f.gravityPotentialFes->GetTrueVSize()); const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0); prepared_operator.Prepare(displacement); - const mfem::Vector first = prepared_test::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.27 - ); - const mfem::Vector second = prepared_test::make_deterministic_vector( - f.densityFes->GetTrueVSize(), 0.79 - ); - const mfem::Vector combination = - prepared_test::linear_combination(first, 1.3, second, -0.6); - const mfem::Vector stellar_density = - prepared_test::make_domain_supported_density(f, true); - const mfem::Vector vacuum_density = - prepared_test::make_domain_supported_density(f, false); + const mfem::Vector first = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.27); + const mfem::Vector second = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.79); + const mfem::Vector combination = prepared_test::linear_combination(first, 1.3, second, -0.6); + const mfem::Vector stellar_density = prepared_test::make_domain_supported_density(f, true); + const mfem::Vector vacuum_density = prepared_test::make_domain_supported_density(f, false); mfem::Vector first_action; mfem::Vector second_action; @@ -123,20 +93,14 @@ TEST_CASE( prepared_operator.Mult(stellar_density, stellar_action); prepared_operator.Mult(vacuum_density, vacuum_action); - const mfem::Vector expected_combination = prepared_test::linear_combination( - first_action, 1.3, second_action, -0.6 - ); - const MPI_Comm communicator = f.mesh->GetComm(); + const mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.3, second_action, -0.6); + const MPI_Comm communicator = f.mesh->GetComm(); - const double linearity_error = prepared_test::relative_error( - combination_action, expected_combination, communicator - ); - const double stellar_norm = - prepared_test::global_norm(stellar_action, communicator); - const double vacuum_norm = - prepared_test::global_norm(vacuum_action, communicator); - const std::uint64_t preparation_count = - prepared_operator.GetPreparationCount(); + const double linearity_error = + prepared_test::relative_error(combination_action, expected_combination, communicator); + const double stellar_norm = prepared_test::global_norm(stellar_action, communicator); + const double vacuum_norm = prepared_test::global_norm(vacuum_action, communicator); + const std::uint64_t preparation_count = prepared_operator.GetPreparationCount(); mfem::Vector repeated_action; prepared_operator.Mult(first, repeated_action); @@ -148,10 +112,6 @@ TEST_CASE( CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12)); CHECK(stellar_norm > 0.0); CHECK(vacuum_norm <= 1.0e-13 * stellar_norm); - CHECK( - prepared_test::relative_error( - repeated_action, first_action, communicator - ) < 2.0e-14 - ); + CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14); CHECK(prepared_operator.GetPreparationCount() == preparation_count); } diff --git a/tests/operators/prepared_hdiv_mass.cpp b/tests/operators/prepared_hdiv_mass.cpp index f19f6c3..57e54ad 100644 --- a/tests/operators/prepared_hdiv_mass.cpp +++ b/tests/operators/prepared_hdiv_mass.cpp @@ -16,22 +16,17 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - operators::PreparedMappedHDivMassOperator prepared_operator( - f, *f.domainMapperStateless - ); + operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless); const mfem::Vector gravity_gradient = - prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.21 - ); + prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.21); const MPI_Comm communicator = f.gravityFluxFes->GetComm(); mfem::Vector identity_action; mfem::Vector deformed_action; for (const double deformation_scale : {0.0, 1.0}) { - const mfem::Vector displacement = - prepared_test::make_displacement(f, deformation_scale); + const mfem::Vector displacement = prepared_test::make_displacement(f, deformation_scale); prepared_operator.Prepare(displacement); @@ -40,23 +35,14 @@ TEST_CASE( prepared_operator.Mult(gravity_gradient, prepared_action); operators::kernels::apply_mapped_hdiv_mass( - f, *f.domainMapperStateless, gravity_gradient, displacement, - reference_action + f, *f.domainMapperStateless, gravity_gradient, displacement, reference_action ); - const double relative_error = prepared_test::relative_error( - prepared_action, reference_action, communicator - ); + const double relative_error = prepared_test::relative_error(prepared_action, reference_action, communicator); INFO("Deformation scale = " << deformation_scale); - INFO( - "Prepared action norm = " - << prepared_test::global_norm(prepared_action, communicator) - ); - INFO( - "Reference action norm = " - << prepared_test::global_norm(reference_action, communicator) - ); + INFO("Prepared action norm = " << prepared_test::global_norm(prepared_action, communicator)); + INFO("Reference action norm = " << prepared_test::global_norm(reference_action, communicator)); INFO("Relative prepared-operator error = " << relative_error); REQUIRE(prepared_operator.IsPrepared()); @@ -69,9 +55,7 @@ TEST_CASE( } } - const double geometry_change = prepared_test::relative_error( - deformed_action, identity_action, communicator - ); + const double geometry_change = prepared_test::relative_error(deformed_action, identity_action, communicator); INFO("Relative action change under deformation = " << geometry_change); @@ -86,20 +70,13 @@ TEST_CASE( auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); - operators::PreparedMappedHDivMassOperator prepared_operator( - f, *f.domainMapperStateless - ); + operators::PreparedMappedHDivMassOperator prepared_operator(f, *f.domainMapperStateless); const mfem::Vector displacement = prepared_test::make_displacement(f, 1.0); prepared_operator.Prepare(displacement); - const mfem::Vector first = prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.17 - ); - const mfem::Vector second = prepared_test::make_deterministic_vector( - f.gravityFluxFes->GetTrueVSize(), 0.83 - ); - const mfem::Vector combination = - prepared_test::linear_combination(first, 1.7, second, -0.4); + const mfem::Vector first = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.17); + const mfem::Vector second = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.83); + const mfem::Vector combination = prepared_test::linear_combination(first, 1.7, second, -0.4); mfem::Vector first_action; mfem::Vector second_action; @@ -110,32 +87,22 @@ TEST_CASE( prepared_operator.Mult(second, second_action); prepared_operator.Mult(combination, combination_action); - mfem::Vector expected_combination = prepared_test::linear_combination( - first_action, 1.7, second_action, -0.4 - ); + mfem::Vector expected_combination = prepared_test::linear_combination(first_action, 1.7, second_action, -0.4); mfem::Vector zero(first.Size()); zero = 0.0; prepared_operator.Mult(zero, zero_action); - const MPI_Comm communicator = f.gravityFluxFes->GetComm(); + const MPI_Comm communicator = f.gravityFluxFes->GetComm(); - const double first_second_product = - prepared_test::global_dot(first, second_action, communicator); - const double second_first_product = - prepared_test::global_dot(second, first_action, communicator); - const double symmetry_error = prepared_test::relative_scalar_error( - first_second_product, second_first_product - ); - const double linearity_error = prepared_test::relative_error( - combination_action, expected_combination, communicator - ); - const double first_energy = - prepared_test::global_dot(first, first_action, communicator); - const double second_energy = - prepared_test::global_dot(second, second_action, communicator); - const std::uint64_t preparation_count = - prepared_operator.GetPreparationCount(); + const double first_second_product = prepared_test::global_dot(first, second_action, communicator); + const double second_first_product = prepared_test::global_dot(second, first_action, communicator); + const double symmetry_error = prepared_test::relative_scalar_error(first_second_product, second_first_product); + const double linearity_error = + prepared_test::relative_error(combination_action, expected_combination, communicator); + const double first_energy = prepared_test::global_dot(first, first_action, communicator); + const double second_energy = prepared_test::global_dot(second, second_action, communicator); + const std::uint64_t preparation_count = prepared_operator.GetPreparationCount(); mfem::Vector repeated_action; prepared_operator.Mult(first, repeated_action); @@ -149,16 +116,9 @@ TEST_CASE( CHECK_THAT(symmetry_error, WithinAbs(0.0, 2.0e-12)); CHECK_THAT(linearity_error, WithinAbs(0.0, 2.0e-12)); - CHECK_THAT( - prepared_test::global_norm(zero_action, communicator), - WithinAbs(0.0, 1.0e-14) - ); + CHECK_THAT(prepared_test::global_norm(zero_action, communicator), WithinAbs(0.0, 1.0e-14)); CHECK(first_energy > 0.0); CHECK(second_energy > 0.0); - CHECK( - prepared_test::relative_error( - repeated_action, first_action, communicator - ) < 2.0e-14 - ); + CHECK(prepared_test::relative_error(repeated_action, first_action, communicator) < 2.0e-14); CHECK(prepared_operator.GetPreparationCount() == preparation_count); } \ No newline at end of file diff --git a/tests/operators/prepared_hydrostatic_equilibrium.cpp b/tests/operators/prepared_hydrostatic_equilibrium.cpp index 7d44708..f4b926e 100644 --- a/tests/operators/prepared_hydrostatic_equilibrium.cpp +++ b/tests/operators/prepared_hydrostatic_equilibrium.cpp @@ -5,9 +5,7 @@ import mean_field; import test_helpers; namespace prepared_hydrostatic_test_utils { - static mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + static mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 211, .revision = 2}, .enthalpy = {.identity = 223, .revision = 3}, @@ -18,8 +16,7 @@ namespace prepared_hydrostatic_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement, @@ -37,18 +34,14 @@ namespace prepared_hydrostatic_test_utils { const mean_field::fem::FEM &f, const double phase = 0.19 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase); } mfem::Vector make_gravity_potential( const mean_field::fem::FEM &f, const double phase = 0.37 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase); } mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) { @@ -72,39 +65,30 @@ TEST_CASE( "Prepared Hydrostatic Residual Matches Stateless Kernel", tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - const mfem::Vector enthalpy = - prepared_hydrostatic_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f); - const mfem::Vector gravityPotential = - prepared_hydrostatic_test_utils::make_gravity_potential(f); + const mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.73); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73); - constexpr double bernoulliConstant = 0.41; + constexpr double bernoulliConstant = 0.41; - const mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_test_utils::make_rotation(); - const auto dependencies = - prepared_hydrostatic_test_utils::make_dependencies(); + const auto dependencies = prepared_hydrostatic_test_utils::make_dependencies(); CHECK_FALSE(preparedOperator.IsPrepared()); CHECK(preparedOperator.GetResidualPreparationCount() == 0); CHECK(preparedOperator.GetResidualApplicationCount() == 0); const auto report = preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, rotation ); @@ -114,13 +98,12 @@ TEST_CASE( preparedOperator.BuildResidual(preparedResidual); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, - displacement, bernoulliConstant, referenceResidual + f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacement, bernoulliConstant, + referenceResidual ); - const double relativeError = gravity_prepared_test_utils::relative_error( - preparedResidual, referenceResidual, f.mesh->GetComm() - ); + const double relativeError = + gravity_prepared_test_utils::relative_error(preparedResidual, referenceResidual, f.mesh->GetComm()); INFO("Prepared hydrostatic residual relative error = " << relativeError); @@ -150,41 +133,33 @@ TEST_CASE( "Prepared Hydrostatic Residual Reuses And Selectively Rebuilds Data", tags::barotrope &tags::hydro &tags::prepared &tags::residuals &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f); + mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f); - mfem::Vector gravityPotential = - prepared_hydrostatic_test_utils::make_gravity_potential(f); + mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f); - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.42); + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42); - double bernoulliConstant = 0.36; + double bernoulliConstant = 0.36; const mfem::Vector initialEnthalpy(enthalpy); const mfem::Vector initialGravityPotential(gravityPotential); const mfem::Vector initialDisplacement(displacement); - const double initialBernoulliConstant = bernoulliConstant; + const double initialBernoulliConstant = bernoulliConstant; - const mean_field::physics::RigidRotation initialRotation = - prepared_hydrostatic_test_utils::make_rotation(0.80); + const mean_field::physics::RigidRotation initialRotation = prepared_hydrostatic_test_utils::make_rotation(0.80); - const mean_field::physics::RigidRotation changedRotation = - prepared_hydrostatic_test_utils::make_rotation(1.25); + const mean_field::physics::RigidRotation changedRotation = prepared_hydrostatic_test_utils::make_rotation(1.25); - auto dependencies = prepared_hydrostatic_test_utils::make_dependencies(); + auto dependencies = prepared_hydrostatic_test_utils::make_dependencies(); preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, initialRotation ); @@ -197,9 +172,7 @@ TEST_CASE( bernoulliConstant += 0.23; const auto unchangedReport = preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, changedRotation ); @@ -209,11 +182,7 @@ TEST_CASE( CHECK_FALSE(unchangedReport.DidAnyWork()); CHECK_FALSE(unchangedReport.updatedRotation); CHECK_FALSE(unchangedReport.preparedResidual); - CHECK( - gravity_prepared_test_utils::relative_error( - unchangedResidual, initialResidual, f.mesh->GetComm() - ) == 0.0 - ); + CHECK(gravity_prepared_test_utils::relative_error(unchangedResidual, initialResidual, f.mesh->GetComm()) == 0.0); CHECK(preparedOperator.GetResidualPreparationCount() == 1); // Only the enthalpy stamp changes. The altered potential, @@ -221,9 +190,7 @@ TEST_CASE( ++dependencies.enthalpy.revision; const auto enthalpyReport = preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, changedRotation ); @@ -233,9 +200,8 @@ TEST_CASE( preparedOperator.BuildResidual(enthalpyResidual); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, initialRotation, enthalpy, - initialGravityPotential, initialDisplacement, initialBernoulliConstant, - enthalpyReference + f, *f.domainMapperStateless, initialRotation, enthalpy, initialGravityPotential, initialDisplacement, + initialBernoulliConstant, enthalpyReference ); CHECK_FALSE(enthalpyReport.contextReport.preparedStaticDependencies); @@ -245,17 +211,13 @@ TEST_CASE( CHECK_FALSE(enthalpyReport.updatedRotation); CHECK(enthalpyReport.preparedResidual); CHECK( - gravity_prepared_test_utils::relative_error( - enthalpyResidual, enthalpyReference, f.mesh->GetComm() - ) < 2.0e-12 + gravity_prepared_test_utils::relative_error(enthalpyResidual, enthalpyReference, f.mesh->GetComm()) < 2.0e-12 ); ++dependencies.rotation.revision; const auto rotationReport = preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, changedRotation ); @@ -265,9 +227,8 @@ TEST_CASE( preparedOperator.BuildResidual(rotationResidual); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, changedRotation, enthalpy, - initialGravityPotential, initialDisplacement, initialBernoulliConstant, - rotationReference + f, *f.domainMapperStateless, changedRotation, enthalpy, initialGravityPotential, initialDisplacement, + initialBernoulliConstant, rotationReference ); CHECK_FALSE(rotationReport.contextReport.preparedStaticDependencies); @@ -277,17 +238,13 @@ TEST_CASE( CHECK(rotationReport.updatedRotation); CHECK(rotationReport.preparedResidual); CHECK( - gravity_prepared_test_utils::relative_error( - rotationResidual, rotationReference, f.mesh->GetComm() - ) < 2.0e-12 + gravity_prepared_test_utils::relative_error(rotationResidual, rotationReference, f.mesh->GetComm()) < 2.0e-12 ); ++dependencies.displacement.revision; const auto displacementReport = preparedOperator.Prepare( - prepared_hydrostatic_test_utils::make_state( - enthalpy, gravityPotential, displacement, bernoulliConstant - ), + prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, bernoulliConstant), dependencies, changedRotation ); @@ -297,9 +254,8 @@ TEST_CASE( preparedOperator.BuildResidual(displacementResidual); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, changedRotation, enthalpy, - initialGravityPotential, displacement, initialBernoulliConstant, - displacementReference + f, *f.domainMapperStateless, changedRotation, enthalpy, initialGravityPotential, displacement, + initialBernoulliConstant, displacementReference ); CHECK_FALSE(displacementReport.contextReport.preparedStaticDependencies); @@ -309,9 +265,8 @@ TEST_CASE( CHECK_FALSE(displacementReport.updatedRotation); CHECK(displacementReport.preparedResidual); CHECK( - gravity_prepared_test_utils::relative_error( - displacementResidual, displacementReference, f.mesh->GetComm() - ) < 2.0e-12 + gravity_prepared_test_utils::relative_error(displacementResidual, displacementReference, f.mesh->GetComm()) < + 2.0e-12 ); const auto &statistics = preparedOperator.GetContextPreparationStatistics(); @@ -324,9 +279,7 @@ TEST_CASE( CHECK(preparedOperator.GetResidualApplicationCount() == 5); const double displacementEffect = - gravity_prepared_test_utils::relative_error( - displacementResidual, rotationResidual, f.mesh->GetComm() - ); + gravity_prepared_test_utils::relative_error(displacementResidual, rotationResidual, f.mesh->GetComm()); INFO("Residual change after displacement update = " << displacementEffect); diff --git a/tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp b/tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp index 0e15b5d..73906ce 100644 --- a/tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp +++ b/tests/operators/prepared_hydrostatic_equilibrium_analytic_accuracy.cpp @@ -24,8 +24,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { public: EnthalpyJacobianOperator( const int enthalpySize, - const mean_field::operators::PreparedHydrostaticEquilibriumOperator - &preparedOperator + const mean_field::operators::PreparedHydrostaticEquilibriumOperator &preparedOperator ) : mfem::Operator(enthalpySize), m_preparedOperator(preparedOperator) { @@ -39,13 +38,10 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { } private: - const mean_field::operators::PreparedHydrostaticEquilibriumOperator - &m_preparedOperator; + const mean_field::operators::PreparedHydrostaticEquilibriumOperator &m_preparedOperator; }; - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 701, .revision = 2}, .enthalpy = {.identity = 709, .revision = 3}, @@ -56,8 +52,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement @@ -84,11 +79,9 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { return vector; } - mean_field::physics::RigidRotation - make_rotation(const AnalyticCase &analyticCase) { + mean_field::physics::RigidRotation make_rotation(const AnalyticCase &analyticCase) { return mean_field::physics::RigidRotation( - make_vector(analyticCase.angularVelocity), - make_vector(analyticCase.rotationCenter) + make_vector(analyticCase.angularVelocity), make_vector(analyticCase.rotationCenter) ); } @@ -101,9 +94,7 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { for (int component = 0; component < 3; ++component) { physicalPosition(component) = - analyticCase - .deformationScale[static_cast(component)] * - referencePosition(component); + analyticCase.deformationScale[static_cast(component)] * referencePosition(component); } } @@ -111,11 +102,9 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { double normalizedRadiusSquared = 0.0; for (int component = 0; component < 3; ++component) { - const double normalizedCoordinate = - referencePosition(component) / mean_field::utils::RADIUS; + const double normalizedCoordinate = referencePosition(component) / mean_field::utils::RADIUS; - normalizedRadiusSquared += - normalizedCoordinate * normalizedCoordinate; + normalizedRadiusSquared += normalizedCoordinate * normalizedCoordinate; } return enthalpyAmplitude * std::max(0.0, 1.0 - normalizedRadiusSquared); @@ -137,20 +126,16 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { * * analytically. */ - return bernoulliConstant + rotation.potential(physicalPosition) - - exact_enthalpy_value(referencePosition); + return bernoulliConstant + rotation.potential(physicalPosition) - exact_enthalpy_value(referencePosition); } - mfem::Array - make_stellar_element_marker(const mean_field::fem::FEM &f) { + mfem::Array make_stellar_element_marker(const mean_field::fem::FEM &f) { mfem::Array stellarElementMarker(f.mesh->GetNE()); - const int vacuumAttribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { - stellarElementMarker[elementId] = - f.mesh->GetAttribute(elementId) != vacuumAttribute; + stellarElementMarker[elementId] = f.mesh->GetAttribute(elementId) != vacuumAttribute; } return stellarElementMarker; @@ -159,9 +144,8 @@ namespace prepared_hydrostatic_analytic_solve_test_utils { TEST_CASE( "Prepared Hydrostatic Operator Solves Analytic Bernoulli Equilibria", - tags::barotrope &tags::hydro &tags::prepared &tags::integration - &tags::solver &tags::convergence &tags::accuracy - &tags::analytic_comparison + tags::barotrope &tags::hydro &tags::prepared &tags::integration &tags::solver &tags::convergence &tags::accuracy + &tags::analytic_comparison ) { using prepared_hydrostatic_analytic_solve_test_utils::AnalyticCase; @@ -191,70 +175,53 @@ TEST_CASE( .rotationCenter = {0.031, -0.024, 0.018}}} }; - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); const MPI_Comm communicator = f.mesh->GetComm(); const mfem::Array stellarElementMarker = - prepared_hydrostatic_analytic_solve_test_utils:: - make_stellar_element_marker(f); + prepared_hydrostatic_analytic_solve_test_utils::make_stellar_element_marker(f); for (const AnalyticCase &analyticCase : analyticCases) { DYNAMIC_SECTION(analyticCase.name) { const double deformationDeterminant = - analyticCase.deformationScale[0] * - analyticCase.deformationScale[1] * - analyticCase.deformationScale[2]; + analyticCase.deformationScale[0] * analyticCase.deformationScale[1] * analyticCase.deformationScale[2]; REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14); const mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_analytic_solve_test_utils::make_rotation( - analyticCase - ); + prepared_hydrostatic_analytic_solve_test_utils::make_rotation(analyticCase); - auto displacementFunction = [&analyticCase]( - const mfem::Vector - &referencePosition, - mfem::Vector &displacementValue - ) { - mfem::Vector physicalPosition; + auto displacementFunction = + [&analyticCase](const mfem::Vector &referencePosition, mfem::Vector &displacementValue) { + mfem::Vector physicalPosition; - prepared_hydrostatic_analytic_solve_test_utils::map_to_physical( - referencePosition, analyticCase, physicalPosition - ); - - displacementValue.SetSize(3); - displacementValue = physicalPosition; - displacementValue -= referencePosition; - }; - - auto potentialFunction = [&analyticCase, &rotation]( - const mfem::Vector &referencePosition - ) { - return prepared_hydrostatic_analytic_solve_test_utils:: - exact_potential_value( - referencePosition, analyticCase, rotation + prepared_hydrostatic_analytic_solve_test_utils::map_to_physical( + referencePosition, analyticCase, physicalPosition ); + + displacementValue.SetSize(3); + displacementValue = physicalPosition; + displacementValue -= referencePosition; + }; + + auto potentialFunction = [&analyticCase, &rotation](const mfem::Vector &referencePosition) { + return prepared_hydrostatic_analytic_solve_test_utils::exact_potential_value( + referencePosition, analyticCase, rotation + ); }; auto enthalpyFunction = [](const mfem::Vector &referencePosition) { - return prepared_hydrostatic_analytic_solve_test_utils:: - exact_enthalpy_value(referencePosition); + return prepared_hydrostatic_analytic_solve_test_utils::exact_enthalpy_value(referencePosition); }; - mfem::VectorFunctionCoefficient displacementCoefficient( - f.mesh->Dimension(), displacementFunction - ); + mfem::VectorFunctionCoefficient displacementCoefficient(f.mesh->Dimension(), displacementFunction); mfem::FunctionCoefficient potentialCoefficient(potentialFunction); - mfem::FunctionCoefficient exactEnthalpyCoefficient( - enthalpyFunction - ); + mfem::FunctionCoefficient exactEnthalpyCoefficient(enthalpyFunction); /* * Project the prescribed geometry and potential. @@ -284,21 +251,17 @@ TEST_CASE( mfem::ParGridFunction zeroEnthalpyField(f.enthalpyFes.get()); - zeroEnthalpyField = 0.0; + zeroEnthalpyField = 0.0; - const double exactEnthalpyNorm = zeroEnthalpyField.ComputeL2Error( - exactEnthalpyCoefficient, nullptr, &stellarElementMarker - ); + const double exactEnthalpyNorm = + zeroEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker); const double projectionError = - projectedEnthalpyField.ComputeL2Error( - exactEnthalpyCoefficient, nullptr, &stellarElementMarker - ); + projectedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker); REQUIRE(exactEnthalpyNorm > 0.0); - const double relativeProjectionError = - projectionError / exactEnthalpyNorm; + const double relativeProjectionError = projectionError / exactEnthalpyNorm; /* * Begin deliberately far from equilibrium. @@ -307,16 +270,12 @@ TEST_CASE( enthalpy = 0.0; - auto dependencies = prepared_hydrostatic_analytic_solve_test_utils:: - make_dependencies(); + auto dependencies = prepared_hydrostatic_analytic_solve_test_utils::make_dependencies(); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); const auto initialReport = preparedOperator.Prepare( - prepared_hydrostatic_analytic_solve_test_utils::make_state( - enthalpy, gravityPotential, displacement - ), + prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement), dependencies, rotation ); @@ -327,10 +286,7 @@ TEST_CASE( preparedOperator.BuildResidual(initialResidual); - const double initialResidualNorm = - gravity_prepared_test_utils::global_norm( - initialResidual, communicator - ); + const double initialResidualNorm = gravity_prepared_test_utils::global_norm(initialResidual, communicator); REQUIRE(initialResidualNorm > 1.0e-12); @@ -344,10 +300,9 @@ TEST_CASE( * rotation, and displacement makes this a well-defined * enthalpy solve. */ - prepared_hydrostatic_analytic_solve_test_utils:: - EnthalpyJacobianOperator enthalpyJacobian( - f.enthalpyFes->GetTrueVSize(), preparedOperator - ); + prepared_hydrostatic_analytic_solve_test_utils::EnthalpyJacobianOperator enthalpyJacobian( + f.enthalpyFes->GetTrueVSize(), preparedOperator + ); mfem::Vector rightHandSide(initialResidual); rightHandSide *= -1.0; @@ -375,9 +330,7 @@ TEST_CASE( INFO("Linear solver converged = " << linearSolver.GetConverged()); - INFO( - "Linear solver iterations = " << linearSolver.GetNumIterations() - ); + INFO("Linear solver iterations = " << linearSolver.GetNumIterations()); INFO("Linear solver final norm = " << linearSolver.GetFinalNorm()); @@ -392,9 +345,7 @@ TEST_CASE( ++dependencies.enthalpy.revision; const auto solvedReport = preparedOperator.Prepare( - prepared_hydrostatic_analytic_solve_test_utils::make_state( - enthalpy, gravityPotential, displacement - ), + prepared_hydrostatic_analytic_solve_test_utils::make_state(enthalpy, gravityPotential, displacement), dependencies, rotation ); @@ -410,13 +361,9 @@ TEST_CASE( preparedOperator.BuildResidual(solvedResidual); - const double solvedResidualNorm = - gravity_prepared_test_utils::global_norm( - solvedResidual, communicator - ); + const double solvedResidualNorm = gravity_prepared_test_utils::global_norm(solvedResidual, communicator); - const double residualReduction = - solvedResidualNorm / initialResidualNorm; + const double residualReduction = solvedResidualNorm / initialResidualNorm; /* * Compare the solved field with the continuum analytic @@ -431,12 +378,9 @@ TEST_CASE( solvedEnthalpyField.SetFromTrueDofs(enthalpy); const double solvedAnalyticError = - solvedEnthalpyField.ComputeL2Error( - exactEnthalpyCoefficient, nullptr, &stellarElementMarker - ); + solvedEnthalpyField.ComputeL2Error(exactEnthalpyCoefficient, nullptr, &stellarElementMarker); - const double relativeSolvedAnalyticError = - solvedAnalyticError / exactEnthalpyNorm; + const double relativeSolvedAnalyticError = solvedAnalyticError / exactEnthalpyNorm; INFO("Deformation determinant = " << deformationDeterminant); @@ -446,15 +390,9 @@ TEST_CASE( INFO("Weak residual reduction = " << residualReduction); - INFO( - "Relative analytic projection floor = " - << relativeProjectionError - ); + INFO("Relative analytic projection floor = " << relativeProjectionError); - INFO( - "Relative solved analytic L2 error = " - << relativeSolvedAnalyticError - ); + INFO("Relative solved analytic L2 error = " << relativeSolvedAnalyticError); /* * The discrete Bernoulli equation must be solved essentially @@ -469,10 +407,7 @@ TEST_CASE( * also contains potential-projection and mapped-space * compatibility errors. */ - CHECK( - relativeSolvedAnalyticError < - std::max(5.0 * relativeProjectionError, 1.25e-4) - ); + CHECK(relativeSolvedAnalyticError < std::max(5.0 * relativeProjectionError, 1.25e-4)); /* * Record that the analytic error remains within one order of diff --git a/tests/operators/prepared_hydrostatic_equilibrium_complete_jacobian.cpp b/tests/operators/prepared_hydrostatic_equilibrium_complete_jacobian.cpp index b9d0d2d..8482ea9 100644 --- a/tests/operators/prepared_hydrostatic_equilibrium_complete_jacobian.cpp +++ b/tests/operators/prepared_hydrostatic_equilibrium_complete_jacobian.cpp @@ -5,9 +5,7 @@ import mean_field; import test_helpers; namespace prepared_hydrostatic_complete_test_utils { - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 503, .revision = 2}, .enthalpy = {.identity = 509, .revision = 3}, @@ -18,8 +16,7 @@ namespace prepared_hydrostatic_complete_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement, @@ -54,11 +51,9 @@ namespace prepared_hydrostatic_complete_test_utils { const double firstPhase, const double secondPhase ) { - mfem::Vector direction = - gravity_prepared_test_utils::make_displacement(f, firstPhase); + mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, firstPhase); - const mfem::Vector secondField = - gravity_prepared_test_utils::make_displacement(f, secondPhase); + const mfem::Vector secondField = gravity_prepared_test_utils::make_displacement(f, secondPhase); direction -= secondField; return direction; @@ -95,25 +90,21 @@ namespace prepared_hydrostatic_complete_test_utils { displacementPlus.Add(step, displacementVariation); displacementMinus.Add(-step, displacementVariation); - const double bernoulliConstantPlus = - baseBernoulliConstant + step * bernoulliConstantVariation; + const double bernoulliConstantPlus = baseBernoulliConstant + step * bernoulliConstantVariation; - const double bernoulliConstantMinus = - baseBernoulliConstant - step * bernoulliConstantVariation; + const double bernoulliConstantMinus = baseBernoulliConstant - step * bernoulliConstantVariation; mfem::Vector residualPlus; mfem::Vector residualMinus; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpyPlus, - gravityPotentialPlus, displacementPlus, bernoulliConstantPlus, - residualPlus + f, *f.domainMapperStateless, rotation, enthalpyPlus, gravityPotentialPlus, displacementPlus, + bernoulliConstantPlus, residualPlus ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpyMinus, - gravityPotentialMinus, displacementMinus, bernoulliConstantMinus, - residualMinus + f, *f.domainMapperStateless, rotation, enthalpyMinus, gravityPotentialMinus, displacementMinus, + bernoulliConstantMinus, residualMinus ); difference = residualPlus; @@ -140,34 +131,25 @@ namespace prepared_hydrostatic_complete_test_utils { TEST_CASE( "Prepared Hydrostatic Complete Jacobian Matches Sum And Centered " "Differences", - tags::barotrope &tags::hydro &tags::integration &tags::jacobian - &tags::prepared &tags::self_consistency + tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::prepared &tags::self_consistency ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); const mfem::Vector enthalpy = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.34 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.34); const mfem::Vector gravityPotential = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.57 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.57); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.68); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.68); - constexpr double bernoulliConstant = 0.43; + constexpr double bernoulliConstant = 0.43; - const mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_complete_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_complete_test_utils::make_rotation(); preparedOperator.Prepare( prepared_hydrostatic_complete_test_utils::make_state( @@ -177,19 +159,13 @@ TEST_CASE( ); const mfem::Vector enthalpyVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 1.07 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 1.07); const mfem::Vector gravityPotentialVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 1.31 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 1.31); const mfem::Vector displacementVariation = - prepared_hydrostatic_complete_test_utils::make_displacement_direction( - f, 1.19, 0.38 - ); + prepared_hydrostatic_complete_test_utils::make_displacement_direction(f, 1.19, 0.38); constexpr double bernoulliConstantVariation = -0.37; @@ -199,25 +175,16 @@ TEST_CASE( mfem::Vector displacementAction; mfem::Vector completeAction; - preparedOperator.ApplyEnthalpyJacobianAction( - enthalpyVariation, enthalpyAction - ); + preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, enthalpyAction); - preparedOperator.ApplyGravityPotentialJacobianAction( - gravityPotentialVariation, gravityPotentialAction - ); + preparedOperator.ApplyGravityPotentialJacobianAction(gravityPotentialVariation, gravityPotentialAction); - preparedOperator.ApplyBernoulliConstantJacobianAction( - bernoulliConstantVariation, bernoulliConstantAction - ); + preparedOperator.ApplyBernoulliConstantJacobianAction(bernoulliConstantVariation, bernoulliConstantAction); - preparedOperator.ApplyDisplacementJacobianAction( - displacementVariation, displacementAction - ); + preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, displacementAction); preparedOperator.ApplyCompleteJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, displacementVariation, completeAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, displacementVariation, completeAction ); mfem::Vector summedAction(enthalpyAction); @@ -230,27 +197,20 @@ TEST_CASE( mfem::Vector centeredDifference; prepared_hydrostatic_complete_test_utils::centered_complete_difference( - f, rotation, enthalpy, gravityPotential, displacement, - bernoulliConstant, enthalpyVariation, gravityPotentialVariation, - displacementVariation, bernoulliConstantVariation, finiteDifferenceStep, + f, rotation, enthalpy, gravityPotential, displacement, bernoulliConstant, enthalpyVariation, + gravityPotentialVariation, displacementVariation, bernoulliConstantVariation, finiteDifferenceStep, centeredDifference ); - const double summationError = gravity_prepared_test_utils::relative_error( - completeAction, summedAction, f.mesh->GetComm() - ); + const double summationError = + gravity_prepared_test_utils::relative_error(completeAction, summedAction, f.mesh->GetComm()); const double centeredDifferenceError = - gravity_prepared_test_utils::relative_error( - completeAction, centeredDifference, f.mesh->GetComm() - ); + gravity_prepared_test_utils::relative_error(completeAction, centeredDifference, f.mesh->GetComm()); INFO("Complete-action summation error = " << summationError); - INFO( - "Complete-action centered-difference error = " - << centeredDifferenceError - ); + INFO("Complete-action centered-difference error = " << centeredDifferenceError); CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 1); @@ -261,29 +221,21 @@ TEST_CASE( TEST_CASE( "Prepared Hydrostatic MFEM Adapter Uses Four Block Layout And Reuses " "Preparation", - tags::barotrope &tags::hydro &tags::integration &tags::jacobian - &tags::mfem_operators &tags::prepared &tags::unit + tags::barotrope &tags::hydro &tags::integration &tags::jacobian &tags::mfem_operators &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); const mfem::Vector enthalpy = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 0.41 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 0.41); const mfem::Vector gravityPotential = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 0.63 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.63); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.74); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.74); constexpr double bernoulliConstant = 0.38; @@ -295,49 +247,31 @@ TEST_CASE( prepared_hydrostatic_complete_test_utils::make_rotation() ); - mean_field::operators::PreparedHydrostaticEquilibriumJacobianOperator - adapter(f, preparedOperator); + mean_field::operators::PreparedHydrostaticEquilibriumJacobianOperator adapter(f, preparedOperator); const auto &layout = adapter.GetLayout(); - CHECK( - layout.Offset( - mean_field::operators::HydrostaticJacobianInputBlock::enthalpy - ) == 0 - ); + CHECK(layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::enthalpy) == 0); CHECK( - layout.Offset( - mean_field::operators::HydrostaticJacobianInputBlock:: - gravityPotential - ) == f.enthalpyFes->GetTrueVSize() + layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential) == + f.enthalpyFes->GetTrueVSize() ); - CHECK( - layout.Size( - mean_field::operators::HydrostaticJacobianInputBlock:: - bernoulliConstant - ) == 1 - ); + CHECK(layout.Size(mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant) == 1); CHECK(adapter.Width() == layout.GetTotalSize()); CHECK(adapter.Height() == layout.GetResidualSize()); CHECK(adapter.Height() == f.enthalpyFes->GetTrueVSize()); const mfem::Vector enthalpyVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), 1.12 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), 1.12); const mfem::Vector gravityPotentialVariation = - gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), 1.39 - ); + gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 1.39); const mfem::Vector displacementVariation = - prepared_hydrostatic_complete_test_utils::make_displacement_direction( - f, 1.28, 0.49 - ); + prepared_hydrostatic_complete_test_utils::make_displacement_direction(f, 1.28, 0.49); constexpr double bernoulliConstantVariation = 0.29; @@ -345,49 +279,39 @@ TEST_CASE( packedDirection = 0.0; prepared_hydrostatic_complete_test_utils::set_block( - packedDirection, layout, - mean_field::operators::HydrostaticJacobianInputBlock::enthalpy, - enthalpyVariation + packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::enthalpy, enthalpyVariation ); prepared_hydrostatic_complete_test_utils::set_block( - packedDirection, layout, - mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential, + packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::gravityPotential, gravityPotentialVariation ); prepared_hydrostatic_complete_test_utils::set_block( - packedDirection, layout, - mean_field::operators::HydrostaticJacobianInputBlock::displacement, + packedDirection, layout, mean_field::operators::HydrostaticJacobianInputBlock::displacement, displacementVariation ); - packedDirection(layout.Offset( - mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant - )) = bernoulliConstantVariation; + packedDirection(layout.Offset(mean_field::operators::HydrostaticJacobianInputBlock::bernoulliConstant)) = + bernoulliConstantVariation; mfem::Vector directAction; mfem::Vector adapterAction; preparedOperator.ApplyCompleteJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, displacementVariation, directAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, displacementVariation, directAction ); adapter.Mult(packedDirection, adapterAction); - const double adapterError = gravity_prepared_test_utils::relative_error( - adapterAction, directAction, f.mesh->GetComm() - ); + const double adapterError = + gravity_prepared_test_utils::relative_error(adapterAction, directAction, f.mesh->GetComm()); - const auto contextStatisticsBefore = - preparedOperator.GetContextPreparationStatistics(); + const auto contextStatisticsBefore = preparedOperator.GetContextPreparationStatistics(); - const auto algebraicStatisticsBefore = - preparedOperator.GetAlgebraicJacobianStatistics(); + const auto algebraicStatisticsBefore = preparedOperator.GetAlgebraicJacobianStatistics(); - const auto displacementStatisticsBefore = - preparedOperator.GetDisplacementJacobianStatistics(); + const auto displacementStatisticsBefore = preparedOperator.GetDisplacementJacobianStatistics(); mfem::Vector secondPackedDirection(packedDirection); secondPackedDirection *= -0.61; @@ -399,18 +323,13 @@ TEST_CASE( expectedSecondAction *= -0.61; const double adapterLinearityError = - gravity_prepared_test_utils::relative_error( - secondAdapterAction, expectedSecondAction, f.mesh->GetComm() - ); + gravity_prepared_test_utils::relative_error(secondAdapterAction, expectedSecondAction, f.mesh->GetComm()); - const auto &contextStatisticsAfter = - preparedOperator.GetContextPreparationStatistics(); + const auto &contextStatisticsAfter = preparedOperator.GetContextPreparationStatistics(); - const auto &algebraicStatisticsAfter = - preparedOperator.GetAlgebraicJacobianStatistics(); + const auto &algebraicStatisticsAfter = preparedOperator.GetAlgebraicJacobianStatistics(); - const auto &displacementStatisticsAfter = - preparedOperator.GetDisplacementJacobianStatistics(); + const auto &displacementStatisticsAfter = preparedOperator.GetDisplacementJacobianStatistics(); INFO("MFEM adapter/direct-action error = " << adapterError); @@ -422,15 +341,9 @@ TEST_CASE( CHECK(contextStatisticsAfter == contextStatisticsBefore); - CHECK( - algebraicStatisticsAfter.preparations == - algebraicStatisticsBefore.preparations - ); + CHECK(algebraicStatisticsAfter.preparations == algebraicStatisticsBefore.preparations); - CHECK( - displacementStatisticsAfter.preparations == - displacementStatisticsBefore.preparations - ); + CHECK(displacementStatisticsAfter.preparations == displacementStatisticsBefore.preparations); CHECK(preparedOperator.GetCompleteJacobianStatistics().applications == 3); } diff --git a/tests/operators/prepared_hydrostatic_equilibrium_displacement_jacobian.cpp b/tests/operators/prepared_hydrostatic_equilibrium_displacement_jacobian.cpp index 7d74c1d..38c5a7e 100644 --- a/tests/operators/prepared_hydrostatic_equilibrium_displacement_jacobian.cpp +++ b/tests/operators/prepared_hydrostatic_equilibrium_displacement_jacobian.cpp @@ -5,9 +5,7 @@ import mean_field; import test_helpers; namespace prepared_hydrostatic_displacement_test_utils { - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 401, .revision = 2}, .enthalpy = {.identity = 409, .revision = 3}, @@ -18,8 +16,7 @@ namespace prepared_hydrostatic_displacement_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement, @@ -37,18 +34,14 @@ namespace prepared_hydrostatic_displacement_test_utils { const mean_field::fem::FEM &f, const double phase = 0.29 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase); } mfem::Vector make_gravity_potential( const mean_field::fem::FEM &f, const double phase = 0.47 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase); } mfem::Vector make_displacement_direction( @@ -56,11 +49,9 @@ namespace prepared_hydrostatic_displacement_test_utils { const double firstPhase, const double secondPhase ) { - mfem::Vector direction = - gravity_prepared_test_utils::make_displacement(f, firstPhase); + mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, firstPhase); - const mfem::Vector secondField = - gravity_prepared_test_utils::make_displacement(f, secondPhase); + const mfem::Vector secondField = gravity_prepared_test_utils::make_displacement(f, secondPhase); direction -= secondField; return direction; @@ -103,13 +94,13 @@ namespace prepared_hydrostatic_displacement_test_utils { mfem::Vector residualMinus; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, - displacementPlus, bernoulliConstant, residualPlus + f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacementPlus, bernoulliConstant, + residualPlus ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, - displacementMinus, bernoulliConstant, residualMinus + f, *f.domainMapperStateless, rotation, enthalpy, gravityPotential, displacementMinus, bernoulliConstant, + residualMinus ); difference = residualPlus; @@ -122,9 +113,7 @@ namespace prepared_hydrostatic_displacement_test_utils { const mfem::Vector &expected, const MPI_Comm communicator ) { - return gravity_prepared_test_utils::relative_error( - actual, expected, communicator - ); + return gravity_prepared_test_utils::relative_error(actual, expected, communicator); } } // namespace prepared_hydrostatic_displacement_test_utils @@ -132,32 +121,26 @@ TEST_CASE( "Prepared Hydrostatic Displacement Jacobian Matches Centered Differences", tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - const mfem::Vector enthalpy = - prepared_hydrostatic_displacement_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = prepared_hydrostatic_displacement_test_utils::make_enthalpy(f); - const mfem::Vector gravityPotential = - prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f); + const mfem::Vector gravityPotential = prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.73); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73); - constexpr double bernoulliConstant = 0.39; + constexpr double bernoulliConstant = 0.39; const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_displacement_test_utils::make_rotation(0.9); - const auto dependencies = - prepared_hydrostatic_displacement_test_utils::make_dependencies(); + const auto dependencies = prepared_hydrostatic_displacement_test_utils::make_dependencies(); - const auto report = preparedOperator.Prepare( + const auto report = preparedOperator.Prepare( prepared_hydrostatic_displacement_test_utils::make_state( enthalpy, gravityPotential, displacement, bernoulliConstant ), @@ -165,12 +148,10 @@ TEST_CASE( ); const mfem::Vector firstVariation = - prepared_hydrostatic_displacement_test_utils:: - make_displacement_direction(f, 1.17, 0.31); + prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.17, 0.31); const mfem::Vector secondVariation = - prepared_hydrostatic_displacement_test_utils:: - make_displacement_direction(f, 1.43, 0.58); + prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.43, 0.58); mfem::Vector combinedVariation(firstVariation); combinedVariation += secondVariation; @@ -179,51 +160,36 @@ TEST_CASE( mfem::Vector secondAction; mfem::Vector combinedAction; - preparedOperator.ApplyDisplacementJacobianAction( - firstVariation, firstAction - ); + preparedOperator.ApplyDisplacementJacobianAction(firstVariation, firstAction); - preparedOperator.ApplyDisplacementJacobianAction( - secondVariation, secondAction - ); + preparedOperator.ApplyDisplacementJacobianAction(secondVariation, secondAction); - preparedOperator.ApplyDisplacementJacobianAction( - combinedVariation, combinedAction - ); + preparedOperator.ApplyDisplacementJacobianAction(combinedVariation, combinedAction); constexpr double finiteDifferenceStep = 1.0e-5; mfem::Vector centeredDifference; - prepared_hydrostatic_displacement_test_utils:: - centered_displacement_difference( - f, rotation, enthalpy, gravityPotential, displacement, - firstVariation, bernoulliConstant, finiteDifferenceStep, - centeredDifference - ); + prepared_hydrostatic_displacement_test_utils::centered_displacement_difference( + f, rotation, enthalpy, gravityPotential, displacement, firstVariation, bernoulliConstant, finiteDifferenceStep, + centeredDifference + ); mfem::Vector sumOfActions(firstAction); sumOfActions += secondAction; - const double centeredDifferenceError = - prepared_hydrostatic_displacement_test_utils::relative_error( - firstAction, centeredDifference, f.mesh->GetComm() - ); + const double centeredDifferenceError = prepared_hydrostatic_displacement_test_utils::relative_error( + firstAction, centeredDifference, f.mesh->GetComm() + ); const double linearityError = - prepared_hydrostatic_displacement_test_utils::relative_error( - combinedAction, sumOfActions, f.mesh->GetComm() - ); + prepared_hydrostatic_displacement_test_utils::relative_error(combinedAction, sumOfActions, f.mesh->GetComm()); - INFO( - "Prepared displacement centered-difference error = " - << centeredDifferenceError - ); + INFO("Prepared displacement centered-difference error = " << centeredDifferenceError); INFO("Prepared displacement linearity error = " << linearityError); - const auto &statistics = - preparedOperator.GetDisplacementJacobianStatistics(); + const auto &statistics = preparedOperator.GetDisplacementJacobianStatistics(); CHECK(report.preparedDisplacementJacobianData); CHECK(statistics.preparations == 1); @@ -237,32 +203,23 @@ TEST_CASE( "Data", tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - const mfem::Vector enthalpy = - prepared_hydrostatic_displacement_test_utils::make_enthalpy(f, 0.37); + const mfem::Vector enthalpy = prepared_hydrostatic_displacement_test_utils::make_enthalpy(f, 0.37); - const mfem::Vector gravityPotential = - prepared_hydrostatic_displacement_test_utils::make_gravity_potential( - f, 0.53 - ); + const mfem::Vector gravityPotential = prepared_hydrostatic_displacement_test_utils::make_gravity_potential(f, 0.53); - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.42); + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42); - constexpr double bernoulliConstant = 0.36; + constexpr double bernoulliConstant = 0.36; - mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_displacement_test_utils::make_rotation(0.75); + mean_field::physics::RigidRotation rotation = prepared_hydrostatic_displacement_test_utils::make_rotation(0.75); - auto dependencies = - prepared_hydrostatic_displacement_test_utils::make_dependencies(); + auto dependencies = prepared_hydrostatic_displacement_test_utils::make_dependencies(); preparedOperator.Prepare( prepared_hydrostatic_displacement_test_utils::make_state( @@ -272,30 +229,21 @@ TEST_CASE( ); const mfem::Vector displacementVariation = - prepared_hydrostatic_displacement_test_utils:: - make_displacement_direction(f, 1.09, 0.27); + prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.09, 0.27); const mfem::Vector secondVariation = - prepared_hydrostatic_displacement_test_utils:: - make_displacement_direction(f, 1.36, 0.64); + prepared_hydrostatic_displacement_test_utils::make_displacement_direction(f, 1.36, 0.64); mfem::Vector initialAction; mfem::Vector secondDirectionAction; - preparedOperator.ApplyDisplacementJacobianAction( - displacementVariation, initialAction - ); + preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, initialAction); - preparedOperator.ApplyDisplacementJacobianAction( - secondVariation, secondDirectionAction - ); + preparedOperator.ApplyDisplacementJacobianAction(secondVariation, secondDirectionAction); - CHECK( - preparedOperator.GetDisplacementJacobianStatistics().preparations == 1 - ); + CHECK(preparedOperator.GetDisplacementJacobianStatistics().preparations == 1); - rotation = - prepared_hydrostatic_displacement_test_utils::make_rotation(1.45); + rotation = prepared_hydrostatic_displacement_test_utils::make_rotation(1.45); ++dependencies.rotation.revision; @@ -308,30 +256,24 @@ TEST_CASE( mfem::Vector rotationUpdatedAction; - preparedOperator.ApplyDisplacementJacobianAction( - displacementVariation, rotationUpdatedAction - ); + preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, rotationUpdatedAction); constexpr double finiteDifferenceStep = 1.0e-5; mfem::Vector rotationReference; - prepared_hydrostatic_displacement_test_utils:: - centered_displacement_difference( - f, rotation, enthalpy, gravityPotential, displacement, - displacementVariation, bernoulliConstant, finiteDifferenceStep, - rotationReference - ); + prepared_hydrostatic_displacement_test_utils::centered_displacement_difference( + f, rotation, enthalpy, gravityPotential, displacement, displacementVariation, bernoulliConstant, + finiteDifferenceStep, rotationReference + ); - const double rotationReferenceError = - prepared_hydrostatic_displacement_test_utils::relative_error( - rotationUpdatedAction, rotationReference, f.mesh->GetComm() - ); + const double rotationReferenceError = prepared_hydrostatic_displacement_test_utils::relative_error( + rotationUpdatedAction, rotationReference, f.mesh->GetComm() + ); - const double rotationEffect = - prepared_hydrostatic_displacement_test_utils::relative_error( - rotationUpdatedAction, initialAction, f.mesh->GetComm() - ); + const double rotationEffect = prepared_hydrostatic_displacement_test_utils::relative_error( + rotationUpdatedAction, initialAction, f.mesh->GetComm() + ); CHECK_FALSE(rotationReport.contextReport.preparedGeometryState); CHECK(rotationReport.contextReport.preparedRotationDependencies); @@ -355,54 +297,34 @@ TEST_CASE( mfem::Vector geometryUpdatedAction; - preparedOperator.ApplyDisplacementJacobianAction( - displacementVariation, geometryUpdatedAction - ); + preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, geometryUpdatedAction); mfem::Vector geometryReference; - prepared_hydrostatic_displacement_test_utils:: - centered_displacement_difference( - f, rotation, enthalpy, gravityPotential, displacement, - displacementVariation, bernoulliConstant, finiteDifferenceStep, - geometryReference - ); - - const double geometryReferenceError = - prepared_hydrostatic_displacement_test_utils::relative_error( - geometryUpdatedAction, geometryReference, f.mesh->GetComm() - ); - - const double geometryEffect = - prepared_hydrostatic_displacement_test_utils::relative_error( - geometryUpdatedAction, rotationUpdatedAction, f.mesh->GetComm() - ); - - INFO( - "Rotation-updated displacement Jacobian error = " - << rotationReferenceError + prepared_hydrostatic_displacement_test_utils::centered_displacement_difference( + f, rotation, enthalpy, gravityPotential, displacement, displacementVariation, bernoulliConstant, + finiteDifferenceStep, geometryReference ); - INFO( - "Displacement Jacobian change after rotation update = " - << rotationEffect + const double geometryReferenceError = prepared_hydrostatic_displacement_test_utils::relative_error( + geometryUpdatedAction, geometryReference, f.mesh->GetComm() ); - INFO( - "Geometry-updated displacement Jacobian error = " - << geometryReferenceError + const double geometryEffect = prepared_hydrostatic_displacement_test_utils::relative_error( + geometryUpdatedAction, rotationUpdatedAction, f.mesh->GetComm() ); - INFO( - "Displacement Jacobian change after geometry update = " - << geometryEffect - ); + INFO("Rotation-updated displacement Jacobian error = " << rotationReferenceError); - const auto &contextStatistics = - preparedOperator.GetContextPreparationStatistics(); + INFO("Displacement Jacobian change after rotation update = " << rotationEffect); - const auto &displacementStatistics = - preparedOperator.GetDisplacementJacobianStatistics(); + INFO("Geometry-updated displacement Jacobian error = " << geometryReferenceError); + + INFO("Displacement Jacobian change after geometry update = " << geometryEffect); + + const auto &contextStatistics = preparedOperator.GetContextPreparationStatistics(); + + const auto &displacementStatistics = preparedOperator.GetDisplacementJacobianStatistics(); CHECK(geometryReport.contextReport.preparedGeometryState); CHECK(geometryReport.contextReport.preparedRotationDependencies); diff --git a/tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp b/tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp index 8d89388..a8ea0dc 100644 --- a/tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp +++ b/tests/operators/prepared_hydrostatic_equilibrium_jacobian.cpp @@ -5,9 +5,7 @@ import mean_field; import test_helpers; namespace prepared_hydrostatic_jacobian_test_utils { - mean_field::operators::context::hydrostatic:: - HydrostaticEquilibriumDependencies - make_dependencies() { + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumDependencies make_dependencies() { return { .discretization = {.identity = 307, .revision = 2}, .enthalpy = {.identity = 311, .revision = 3}, @@ -18,8 +16,7 @@ namespace prepared_hydrostatic_jacobian_test_utils { }; } - mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView - make_state( + mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView make_state( const mfem::Vector &enthalpy, const mfem::Vector &gravityPotential, const mfem::Vector &displacement, @@ -37,18 +34,14 @@ namespace prepared_hydrostatic_jacobian_test_utils { const mean_field::fem::FEM &f, const double phase = 0.23 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.enthalpyFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.enthalpyFes->GetTrueVSize(), phase); } mfem::Vector make_gravity_potential( const mean_field::fem::FEM &f, const double phase = 0.41 ) { - return gravity_prepared_test_utils::make_deterministic_vector( - f.gravityPotentialFes->GetTrueVSize(), phase - ); + return gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), phase); } mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) { @@ -83,15 +76,13 @@ namespace prepared_hydrostatic_jacobian_test_utils { mfem::Vector residualMinus; mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpyPlus, - gravityPotentialPlus, displacement, bernoulliConstantPlus, - residualPlus + f, *f.domainMapperStateless, rotation, enthalpyPlus, gravityPotentialPlus, displacement, + bernoulliConstantPlus, residualPlus ); mean_field::operators::kernels::apply_hydrostatic_equilibrium( - f, *f.domainMapperStateless, rotation, enthalpyMinus, - gravityPotentialMinus, displacement, bernoulliConstantMinus, - residualMinus + f, *f.domainMapperStateless, rotation, enthalpyMinus, gravityPotentialMinus, displacement, + bernoulliConstantMinus, residualMinus ); // The two states are separated by one complete variation: @@ -105,9 +96,7 @@ namespace prepared_hydrostatic_jacobian_test_utils { const mfem::Vector &expected, MPI_Comm communicator ) { - return gravity_prepared_test_utils::relative_error( - actual, expected, communicator - ); + return gravity_prepared_test_utils::relative_error(actual, expected, communicator); } } // namespace prepared_hydrostatic_jacobian_test_utils @@ -116,42 +105,33 @@ TEST_CASE( "Differences", tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - const mfem::Vector enthalpy = - prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f); + const mfem::Vector enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f); - const mfem::Vector gravityPotential = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f); + const mfem::Vector gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f); - const mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.73); + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.73); - constexpr double bernoulliConstant = 0.39; + constexpr double bernoulliConstant = 0.39; - const mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_jacobian_test_utils::make_rotation(); + const mean_field::physics::RigidRotation rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(); - const auto report = preparedOperator.Prepare( + const auto report = preparedOperator.Prepare( prepared_hydrostatic_jacobian_test_utils::make_state( enthalpy, gravityPotential, displacement, bernoulliConstant ), prepared_hydrostatic_jacobian_test_utils::make_dependencies(), rotation ); - const mfem::Vector enthalpyVariation = - prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.71); + const mfem::Vector enthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.71); const mfem::Vector gravityPotentialVariation = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential( - f, 0.83 - ); + prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 0.83); constexpr double bernoulliConstantVariation = -0.31; @@ -160,21 +140,14 @@ TEST_CASE( mfem::Vector bernoulliConstantAction; mfem::Vector combinedAction; - preparedOperator.ApplyEnthalpyJacobianAction( - enthalpyVariation, enthalpyAction - ); + preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, enthalpyAction); - preparedOperator.ApplyGravityPotentialJacobianAction( - gravityPotentialVariation, gravityPotentialAction - ); + preparedOperator.ApplyGravityPotentialJacobianAction(gravityPotentialVariation, gravityPotentialAction); - preparedOperator.ApplyBernoulliConstantJacobianAction( - bernoulliConstantVariation, bernoulliConstantAction - ); + preparedOperator.ApplyBernoulliConstantJacobianAction(bernoulliConstantVariation, bernoulliConstantAction); preparedOperator.ApplyAlgebraicJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, combinedAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, combinedAction ); mfem::Vector enthalpyPlus(enthalpy); @@ -188,9 +161,8 @@ TEST_CASE( mfem::Vector enthalpyReference; prepared_hydrostatic_jacobian_test_utils::centered_residual_difference( - f, rotation, enthalpyPlus, enthalpyMinus, gravityPotential, - gravityPotential, displacement, bernoulliConstant, bernoulliConstant, - enthalpyReference + f, rotation, enthalpyPlus, enthalpyMinus, gravityPotential, gravityPotential, displacement, bernoulliConstant, + bernoulliConstant, enthalpyReference ); enthalpyPlus = enthalpy; @@ -203,8 +175,7 @@ TEST_CASE( mfem::Vector gravityPotentialReference; prepared_hydrostatic_jacobian_test_utils::centered_residual_difference( - f, rotation, enthalpy, enthalpy, gravityPotentialPlus, - gravityPotentialMinus, displacement, bernoulliConstant, + f, rotation, enthalpy, enthalpy, gravityPotentialPlus, gravityPotentialMinus, displacement, bernoulliConstant, bernoulliConstant, gravityPotentialReference ); @@ -214,9 +185,8 @@ TEST_CASE( mfem::Vector bernoulliConstantReference; prepared_hydrostatic_jacobian_test_utils::centered_residual_difference( - f, rotation, enthalpy, enthalpy, gravityPotential, gravityPotential, - displacement, bernoulliConstant + 0.5 * bernoulliConstantVariation, - bernoulliConstant - 0.5 * bernoulliConstantVariation, + f, rotation, enthalpy, enthalpy, gravityPotential, gravityPotential, displacement, + bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation, bernoulliConstantReference ); @@ -230,10 +200,9 @@ TEST_CASE( mfem::Vector combinedReference; prepared_hydrostatic_jacobian_test_utils::centered_residual_difference( - f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, - gravityPotentialMinus, displacement, - bernoulliConstant + 0.5 * bernoulliConstantVariation, - bernoulliConstant - 0.5 * bernoulliConstantVariation, combinedReference + f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, gravityPotentialMinus, displacement, + bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation, + combinedReference ); mfem::Vector sumOfBlocks(enthalpyAction); @@ -241,30 +210,21 @@ TEST_CASE( sumOfBlocks += bernoulliConstantAction; const double enthalpyError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - enthalpyAction, enthalpyReference, f.mesh->GetComm() - ); + prepared_hydrostatic_jacobian_test_utils::relative_error(enthalpyAction, enthalpyReference, f.mesh->GetComm()); - const double gravityPotentialError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - gravityPotentialAction, gravityPotentialReference, f.mesh->GetComm() - ); + const double gravityPotentialError = prepared_hydrostatic_jacobian_test_utils::relative_error( + gravityPotentialAction, gravityPotentialReference, f.mesh->GetComm() + ); - const double bernoulliConstantError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - bernoulliConstantAction, bernoulliConstantReference, - f.mesh->GetComm() - ); + const double bernoulliConstantError = prepared_hydrostatic_jacobian_test_utils::relative_error( + bernoulliConstantAction, bernoulliConstantReference, f.mesh->GetComm() + ); const double combinedError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - combinedAction, combinedReference, f.mesh->GetComm() - ); + prepared_hydrostatic_jacobian_test_utils::relative_error(combinedAction, combinedReference, f.mesh->GetComm()); const double blockSumError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - combinedAction, sumOfBlocks, f.mesh->GetComm() - ); + prepared_hydrostatic_jacobian_test_utils::relative_error(combinedAction, sumOfBlocks, f.mesh->GetComm()); INFO("Enthalpy block error = " << enthalpyError); INFO("Gravity-potential block error = " << gravityPotentialError); @@ -293,30 +253,23 @@ TEST_CASE( "Geometry", tags::barotrope &tags::hydro &tags::jacobian &tags::prepared &tags::unit ) { - auto args = test_utils::setup_args(); + auto args = test_utils::setup_args(); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); - mean_field::operators::PreparedHydrostaticEquilibriumOperator - preparedOperator(f, *f.domainMapperStateless); + mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless); - mfem::Vector enthalpy = - prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f); + mfem::Vector enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f); - mfem::Vector gravityPotential = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f); + mfem::Vector gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f); - mfem::Vector displacement = - gravity_prepared_test_utils::make_displacement(f, 0.42); + mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.42); - double bernoulliConstant = 0.37; + double bernoulliConstant = 0.37; - mean_field::physics::RigidRotation rotation = - prepared_hydrostatic_jacobian_test_utils::make_rotation(0.8); + mean_field::physics::RigidRotation rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(0.8); - auto dependencies = - prepared_hydrostatic_jacobian_test_utils::make_dependencies(); + auto dependencies = prepared_hydrostatic_jacobian_test_utils::make_dependencies(); preparedOperator.Prepare( prepared_hydrostatic_jacobian_test_utils::make_state( @@ -325,32 +278,25 @@ TEST_CASE( dependencies, rotation ); - const mfem::Vector enthalpyVariation = - prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.67); + const mfem::Vector enthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 0.67); const mfem::Vector gravityPotentialVariation = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential( - f, 0.79 - ); + prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 0.79); constexpr double bernoulliConstantVariation = 0.28; mfem::Vector initialAction; preparedOperator.ApplyAlgebraicJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, initialAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, initialAction ); - enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.13); + enthalpy = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.13); - gravityPotential = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential( - f, 1.31 - ); + gravityPotential = prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 1.31); bernoulliConstant = 0.62; - rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(1.4); + rotation = prepared_hydrostatic_jacobian_test_utils::make_rotation(1.4); ++dependencies.enthalpy.revision; ++dependencies.gravityPotential.revision; @@ -367,8 +313,7 @@ TEST_CASE( mfem::Vector baseStateChangedAction; preparedOperator.ApplyAlgebraicJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, baseStateChangedAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, baseStateChangedAction ); CHECK_FALSE(baseStateReport.contextReport.preparedGeometryState); @@ -382,19 +327,15 @@ TEST_CASE( ) == 0.0 ); - const mfem::Vector secondEnthalpyVariation = - prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.57); + const mfem::Vector secondEnthalpyVariation = prepared_hydrostatic_jacobian_test_utils::make_enthalpy(f, 1.57); const mfem::Vector secondGravityPotentialVariation = - prepared_hydrostatic_jacobian_test_utils::make_gravity_potential( - f, 1.73 - ); + prepared_hydrostatic_jacobian_test_utils::make_gravity_potential(f, 1.73); mfem::Vector secondDirectionAction; preparedOperator.ApplyAlgebraicJacobianAction( - secondEnthalpyVariation, secondGravityPotentialVariation, -0.19, - secondDirectionAction + secondEnthalpyVariation, secondGravityPotentialVariation, -0.19, secondDirectionAction ); CHECK(preparedOperator.GetAlgebraicJacobianStatistics().preparations == 1); @@ -413,8 +354,7 @@ TEST_CASE( mfem::Vector geometryChangedAction; preparedOperator.ApplyAlgebraicJacobianAction( - enthalpyVariation, gravityPotentialVariation, - bernoulliConstantVariation, geometryChangedAction + enthalpyVariation, gravityPotentialVariation, bernoulliConstantVariation, geometryChangedAction ); mfem::Vector enthalpyPlus(enthalpy); @@ -432,31 +372,23 @@ TEST_CASE( mfem::Vector geometryReference; prepared_hydrostatic_jacobian_test_utils::centered_residual_difference( - f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, - gravityPotentialMinus, displacement, - bernoulliConstant + 0.5 * bernoulliConstantVariation, - bernoulliConstant - 0.5 * bernoulliConstantVariation, geometryReference + f, rotation, enthalpyPlus, enthalpyMinus, gravityPotentialPlus, gravityPotentialMinus, displacement, + bernoulliConstant + 0.5 * bernoulliConstantVariation, bernoulliConstant - 0.5 * bernoulliConstantVariation, + geometryReference ); - const double geometryReferenceError = - prepared_hydrostatic_jacobian_test_utils::relative_error( - geometryChangedAction, geometryReference, f.mesh->GetComm() - ); - - const double geometryEffect = - prepared_hydrostatic_jacobian_test_utils::relative_error( - geometryChangedAction, initialAction, f.mesh->GetComm() - ); - - INFO( - "Geometry-updated algebraic Jacobian error = " << geometryReferenceError + const double geometryReferenceError = prepared_hydrostatic_jacobian_test_utils::relative_error( + geometryChangedAction, geometryReference, f.mesh->GetComm() ); - INFO( - "Algebraic Jacobian change after deformation update = " - << geometryEffect + const double geometryEffect = prepared_hydrostatic_jacobian_test_utils::relative_error( + geometryChangedAction, initialAction, f.mesh->GetComm() ); + INFO("Geometry-updated algebraic Jacobian error = " << geometryReferenceError); + + INFO("Algebraic Jacobian change after deformation update = " << geometryEffect); + const auto &statistics = preparedOperator.GetAlgebraicJacobianStatistics(); CHECK(geometryReport.contextReport.preparedGeometryState); diff --git a/tests/operators/prepared_mass_normalization.cpp b/tests/operators/prepared_mass_normalization.cpp new file mode 100644 index 0000000..59ba16f --- /dev/null +++ b/tests/operators/prepared_mass_normalization.cpp @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +import mean_field; +import test_helpers; + +namespace mass_normalization_test_utils { + using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + [[nodiscard]] mean_field::operators::MassNormalizationLayout make_layout(const mean_field::fem::FEM &f) { + const std::array valueSizes{ + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + const std::array residualSizes{ + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(), + f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + return {valueSizes, residualSizes}; + } + + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.91 + 0.07 * std::sin(0.83 * position(0) + phase) + 0.05 * std::cos(0.61 * position(1) - phase) + + 0.03 * position(2) * position(2); + }); + field.ProjectCoefficient(coefficient); + + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector make_constant_density( + const mean_field::fem::FEM &f, + const double value + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::ConstantCoefficient coefficient(value); + field.ProjectCoefficient(coefficient); + + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector make_density_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.19 * std::sin(0.71 * position(0) + phase) - 0.13 * std::cos(0.89 * position(1) - phase) + + 0.08 * position(2); + }); + field.ProjectCoefficient(coefficient); + + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector make_affine_displacement( + const mean_field::fem::FEM &f, + const double scale + ) { + mfem::ParGridFunction field(f.displacementFes.get()); + mfem::VectorFunctionCoefficient coefficient( + f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(position.Size()); + for (int dimension = 0; dimension < position.Size(); ++dimension) { + value(dimension) = scale * position(dimension); + } + } + ); + field.ProjectCoefficient(coefficient); + + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector make_displacement_direction( + const mean_field::fem::FEM &f, + const double scale + ) { + mfem::ParGridFunction field(f.displacementFes.get()); + mfem::VectorFunctionCoefficient coefficient( + f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2)); + value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2)); + value(2) = scale * (0.04 * position(2) - 0.011 * position(0) * position(1)); + } + ); + field.ProjectCoefficient(coefficient); + + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mean_field::operators::MassNormalizationDependencies make_dependencies() { + return { + .discretization = {.identity = 701, .revision = 3}, + .density = {.identity = 709, .revision = 5}, + .displacement = {.identity = 719, .revision = 7}, + .targetMass = {.identity = 727, .revision = 11} + }; + } + + [[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions make_gravity_revisions( + const mean_field::operators::MassNormalizationDependencies &dependencies, + const std::uint64_t gravityGradientRevision = 13, + const std::uint64_t gravityPotentialRevision = 17 + ) { + return { + .discretization = {.value = dependencies.discretization.revision}, + .displacement = {.value = dependencies.displacement.revision}, + .density = {.value = dependencies.density.revision}, + .gravity_gradient = {.value = gravityGradientRevision}, + .gravity_potential = {.value = gravityPotentialRevision} + }; + } + + void prepare_gravity_context( + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context, + const mean_field::fem::FEM &f, + const mfem::Vector &density, + const mfem::Vector &displacement, + const mean_field::operators::MassNormalizationDependencies &dependencies, + const std::uint64_t gravityGradientRevision = 13, + const std::uint64_t gravityPotentialRevision = 17 + ) { + mfem::Vector gravityGradient(f.gravityFluxFes->GetTrueVSize()); + gravityGradient = 0.0; + + mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize()); + gravityPotential = 0.0; + + context.Prepare( + {.density = density, + .displacement = displacement, + .gravity_gradient = gravityGradient, + .gravity_potential = gravityPotential}, + make_gravity_revisions(dependencies, gravityGradientRevision, gravityPotentialRevision) + ); + } + + [[nodiscard]] double residual_value(const mean_field::operators::PreparedMassNormalizationOperator &massOperator) { + mfem::Vector residual; + massOperator.BuildResidual(residual); + REQUIRE(residual.Size() == 1); + return residual(0); + } + + [[nodiscard]] double relative_error( + const double computed, + const double reference + ) { + return std::abs(computed - reference) / + std::max(std::abs(reference), 100.0 * std::numeric_limits::epsilon()); + } +} // namespace mass_normalization_test_utils + +TEST_CASE( + "Prepared Mass Normalization Has The Analytic Affine Volume Scaling", + tags::barotrope &tags::prepared &tags::analytic_comparison +) { + using Operator = mean_field::operators::PreparedMassNormalizationOperator; + + 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); + + 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 double densityValue = 1.37; + const double targetMass = 0.73; + const double affineScale = 0.086; + + const mfem::Vector density = mass_normalization_test_utils::make_constant_density(f, densityValue); + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + auto dependencies = mass_normalization_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies); + + Operator massOperator(f, *f.domainMapperStateless, gravityContext); + + const auto initialReport = massOperator.Prepare({.targetMass = targetMass}, dependencies); + + CHECK(initialReport.rebuiltStaticPlan); + CHECK(initialReport.refreshedGeometry); + CHECK(initialReport.refreshedDensity); + CHECK(initialReport.updatedTargetMass); + CHECK(initialReport.assembledResidual); + + const double undeformedMass = massOperator.GetCurrentMass(); + const mean_field::mapping::COORDINATE_SPACE volumeCoordinates = + f.has_mapping() ? mean_field::mapping::COORDINATE_SPACE::PHYSICAL + : mean_field::mapping::COORDINATE_SPACE::REFERENCE; + + const double independentlyIntegratedMass = + densityValue * mean_field::analysis::get_mesh_volume(f, volumeCoordinates, mean_field::utils::DOMAINS::STELLAR); + + CHECK(mass_normalization_test_utils::relative_error(undeformedMass, independentlyIntegratedMass) < 1.0e-12); + + CHECK( + mass_normalization_test_utils::relative_error( + mass_normalization_test_utils::residual_value(massOperator), undeformedMass - targetMass + ) < 2.0e-15 + ); + + displacement = mass_normalization_test_utils::make_affine_displacement(f, affineScale); + ++dependencies.displacement.revision; + + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies); + + const auto deformedReport = massOperator.Prepare({.targetMass = targetMass}, dependencies); + + CHECK_FALSE(deformedReport.rebuiltStaticPlan); + CHECK(deformedReport.refreshedGeometry); + CHECK_FALSE(deformedReport.refreshedDensity); + + const double expectedScale = std::pow(1.0 + affineScale, 3); + const double measuredScale = massOperator.GetCurrentMass() / undeformedMass; + + INFO("Expected affine mass scale = " << expectedScale); + INFO("Measured affine mass scale = " << measuredScale); + CHECK(mass_normalization_test_utils::relative_error(measuredScale, expectedScale) < 5e-7); +} + +TEST_CASE( + "Prepared Mass Normalization Density Jacobian Matches Centered Difference", + tags::barotrope &tags::prepared &tags::jacobian &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()); + + mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.31); + const mfem::Vector densityDirection = mass_normalization_test_utils::make_density_direction(f, 0.67); + const mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.43); + + auto dependencies = mass_normalization_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies); + + mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); + massOperator.Prepare({.targetMass = 1.23}, dependencies); + + mfem::Vector analyticAction; + massOperator.ApplyDensityJacobianAction(densityDirection, analyticAction); + + constexpr double epsilon = 1.0e-3; + mfem::Vector densityPlus(density); + densityPlus.Add(epsilon, densityDirection); + ++dependencies.density.revision; + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, densityPlus, displacement, dependencies); + massOperator.Prepare({.targetMass = 1.23}, dependencies); + const double residualPlus = mass_normalization_test_utils::residual_value(massOperator); + + mfem::Vector densityMinus(density); + densityMinus.Add(-epsilon, densityDirection); + ++dependencies.density.revision; + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, densityMinus, displacement, dependencies); + massOperator.Prepare({.targetMass = 1.23}, dependencies); + const double residualMinus = mass_normalization_test_utils::residual_value(massOperator); + + const double finiteDifference = (residualPlus - residualMinus) / (2.0 * epsilon); + + INFO("Density action = " << analyticAction(0)); + INFO("Density centered difference = " << finiteDifference); + CHECK(mass_normalization_test_utils::relative_error(analyticAction(0), finiteDifference) < 3.0e-8); +} + +TEST_CASE( + "Prepared Mass Normalization Geometry Jacobian Matches Centered Difference", + tags::barotrope &tags::prepared &tags::jacobian &tags::geometry +) { + 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 mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.37); + mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.51); + const mfem::Vector displacementDirection = mass_normalization_test_utils::make_displacement_direction(f, -0.79); + + auto dependencies = mass_normalization_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies); + + mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); + massOperator.Prepare({.targetMass = 1.11}, dependencies); + + mfem::Vector analyticAction; + massOperator.ApplyDisplacementJacobianAction(displacementDirection, analyticAction); + + constexpr double epsilon = 1.0e-6; + mfem::Vector displacementPlus(displacement); + displacementPlus.Add(epsilon, displacementDirection); + ++dependencies.displacement.revision; + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacementPlus, dependencies); + massOperator.Prepare({.targetMass = 1.11}, dependencies); + const double residualPlus = mass_normalization_test_utils::residual_value(massOperator); + + mfem::Vector displacementMinus(displacement); + displacementMinus.Add(-epsilon, displacementDirection); + ++dependencies.displacement.revision; + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacementMinus, dependencies); + massOperator.Prepare({.targetMass = 1.11}, dependencies); + const double residualMinus = mass_normalization_test_utils::residual_value(massOperator); + + const double finiteDifference = (residualPlus - residualMinus) / (2.0 * epsilon); + + INFO("Geometry action = " << analyticAction(0)); + INFO("Geometry centered difference = " << finiteDifference); + CHECK(mass_normalization_test_utils::relative_error(analyticAction(0), finiteDifference) < 2.0e-7); +} + +TEST_CASE( + "Prepared Mass Normalization Selectively Refreshes Its Cached State", + tags::barotrope &tags::prepared &tags::contexts &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()); + + mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.29); + mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.41); + auto dependencies = mass_normalization_test_utils::make_dependencies(); + + std::uint64_t gravityPotentialRevision = 17; + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + mass_normalization_test_utils::prepare_gravity_context( + gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision + ); + + mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); + + massOperator.Prepare({.targetMass = 1.0}, dependencies); + const std::uint64_t preparationCount = massOperator.GetPreparationCount(); + + const auto repeated = massOperator.Prepare({.targetMass = 1.0}, dependencies); + CHECK_FALSE(repeated.DidAnyWork()); + CHECK(massOperator.GetPreparationCount() == preparationCount); + + ++gravityPotentialRevision; + mass_normalization_test_utils::prepare_gravity_context( + gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision + ); + + const auto potentialOnly = massOperator.Prepare({.targetMass = 1.0}, dependencies); + CHECK_FALSE(potentialOnly.DidAnyWork()); + + const double residualBeforeTargetChange = mass_normalization_test_utils::residual_value(massOperator); + const double massBeforeTargetChange = massOperator.GetCurrentMass(); + + ++dependencies.targetMass.revision; + const auto targetOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies); + CHECK(targetOnly.updatedTargetMass); + CHECK(targetOnly.assembledResidual); + CHECK_FALSE(targetOnly.rebuiltStaticPlan); + CHECK_FALSE(targetOnly.refreshedGeometry); + CHECK_FALSE(targetOnly.refreshedDensity); + CHECK(massOperator.GetCurrentMass() == massBeforeTargetChange); + CHECK( + mass_normalization_test_utils::relative_error( + mass_normalization_test_utils::residual_value(massOperator) - residualBeforeTargetChange, -0.4 + ) < 2.0e-15 + ); + + const double massBeforeDensityChange = massOperator.GetCurrentMass(); + density = mass_normalization_test_utils::make_density(f, 0.83); + ++dependencies.density.revision; + mass_normalization_test_utils::prepare_gravity_context( + gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision + ); + + const auto densityOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies); + CHECK(densityOnly.refreshedDensity); + CHECK(densityOnly.assembledResidual); + CHECK_FALSE(densityOnly.refreshedGeometry); + CHECK(massOperator.GetCurrentMass() != massBeforeDensityChange); + + displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.87); + ++dependencies.displacement.revision; + mass_normalization_test_utils::prepare_gravity_context( + gravityContext, f, density, displacement, dependencies, 13, gravityPotentialRevision + ); + + const auto geometryOnly = massOperator.Prepare({.targetMass = 1.4}, dependencies); + CHECK(geometryOnly.refreshedGeometry); + CHECK(geometryOnly.assembledResidual); + CHECK_FALSE(geometryOnly.refreshedDensity); +} + +TEST_CASE( + "Prepared Mass Normalization Complete Action And Coupled Routing Are Exact", + tags::barotrope &tags::prepared &tags::jacobian &tags::mfem_operators +) { + 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 mfem::Vector density = mass_normalization_test_utils::make_density(f, 0.47); + const mfem::Vector displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.57); + const mfem::Vector densityDirection = mass_normalization_test_utils::make_density_direction(f, 0.71); + const mfem::Vector displacementDirection = mass_normalization_test_utils::make_displacement_direction(f, -0.63); + + const auto dependencies = mass_normalization_test_utils::make_dependencies(); + + mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext( + f, *f.domainMapperStateless + ); + mass_normalization_test_utils::prepare_gravity_context(gravityContext, f, density, displacement, dependencies); + + mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext); + massOperator.Prepare({.targetMass = 1.19}, dependencies); + + mfem::Vector densityAction; + mfem::Vector displacementAction; + mfem::Vector completeAction; + + massOperator.ApplyDensityJacobianAction(densityDirection, densityAction); + massOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction); + massOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction); + + CHECK( + mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) < + 2.0e-15 + ); + + const auto layout = mass_normalization_test_utils::make_layout(f); + mean_field::operators::PreparedMassNormalizationJacobianOperator adapter(layout, massOperator); + + mfem::Vector direction(layout.value_offsets().Last()); + direction = 0.0; + + for (int entry = 0; entry < densityDirection.Size(); ++entry) { + direction(layout.offset(mass_normalization_test_utils::densityValue) + entry) = densityDirection(entry); + } + + for (int entry = 0; entry < displacementDirection.Size(); ++entry) { + direction(layout.offset(mass_normalization_test_utils::displacementValue) + entry) = + displacementDirection(entry); + } + + mfem::Vector coupledAction; + adapter.Mult(direction, coupledAction); + + const int massOffset = layout.offset(mass_normalization_test_utils::massResidual); + + REQUIRE(coupledAction.Size() == layout.residual_offsets().Last()); + CHECK(coupledAction(massOffset) == completeAction(0)); + + for (int entry = 0; entry < coupledAction.Size(); ++entry) { + if (entry != massOffset) { + CHECK(coupledAction(entry) == 0.0); + } + } + + CHECK(&massOperator.GetFEM() == &f); + CHECK(&massOperator.GetGravityContext() == &gravityContext); + CHECK(adapter.GetLayout().residual_offsets().Last() == layout.residual_offsets().Last()); +} \ No newline at end of file diff --git a/tests/operators/prepared_pressure_force.cpp b/tests/operators/prepared_pressure_force.cpp new file mode 100644 index 0000000..eee9f7d --- /dev/null +++ b/tests/operators/prepared_pressure_force.cpp @@ -0,0 +1,706 @@ +#include +#include +#include +#include +#include + +#include +#include + +import mean_field; +import test_helpers; + +namespace prepared_pressure_force_test_utils { + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form; + + struct Maps final { + mean_field::field::FieldDofMap density; + mean_field::field::FieldDofMap displacement; + mean_field::field::FieldDofMap gravityFlux; + mean_field::field::FieldDofMap gravityPotential; + mean_field::field::FieldDofMap enthalpy; + + explicit Maps(const mean_field::fem::FEM &f) + : density( + mean_field::field::make_field_dof_map< + mean_field::field::Density, + DomainSchema>(*f.densityFes) + ), + displacement( + mean_field::field::make_field_dof_map< + mean_field::field::Displacement, + DomainSchema>(*f.displacementFes) + ), + gravityFlux( + mean_field::field::make_field_dof_map< + mean_field::field::Gravity, + DomainSchema>(*f.gravityFluxFes) + ), + gravityPotential( + mean_field::field::make_field_dof_map< + mean_field::field::Gravity, + DomainSchema>(*f.gravityPotentialFes) + ), + enthalpy( + mean_field::field::make_field_dof_map< + mean_field::field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ) { + } + }; + + [[nodiscard]] + mfem::Vector make_positive_enthalpy_true( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::Vector enthalpy(f.enthalpyFes->GetTrueVSize()); + + for (int index = 0; index < enthalpy.Size(); ++index) { + const double position = static_cast(index + 1); + + enthalpy(index) = + 0.93 + 0.09 * std::sin(0.23 * position + phase) + 0.04 * std::cos(0.17 * position - 0.5 * phase); + } + + return enthalpy; + } + + [[nodiscard]] + mfem::Vector make_enthalpy_direction_true( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::Vector direction(f.enthalpyFes->GetTrueVSize()); + + for (int index = 0; index < direction.Size(); ++index) { + const double position = static_cast(index + 1); + + direction(index) = + 0.27 * std::sin(0.19 * position + phase) + 0.14 * std::cos(0.13 * position - 0.5 * phase); + } + + return direction; + } + + [[nodiscard]] + mfem::Vector make_displacement_direction_true( + const mean_field::fem::FEM &f, + const double phase + ) { + MFEM_VERIFY( + f.mesh->Dimension() == 3, "The prepared pressure-force test requires a " + "three-dimensional mesh." + ); + + mfem::ParGridFunction directionField(f.displacementFes.get()); + + mfem::VectorFunctionCoefficient directionCoefficient( + 3, [phase](const mfem::Vector &position, mfem::Vector &value) { + const double x = position(0); + + const double y = position(1); + + const double z = position(2); + + value.SetSize(3); + + value(0) = 0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * phase * y; + + value(1) = -0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * phase * z; + + value(2) = 0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * phase * x; + } + ); + + directionField.ProjectCoefficient(directionCoefficient); + + mfem::Vector directionTrue; + + directionField.GetTrueDofs(directionTrue); + + return directionTrue; + } + + [[nodiscard]] + double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const MPI_Comm communicator + ) { + MFEM_VERIFY( + left.Size() == right.Size(), "Cannot compare prepared pressure-force vectors with " + "different sizes." + ); + + mfem::Vector difference(left); + + difference -= right; + + const double scale = std::max( + {gravity_prepared_test_utils::global_norm(left, communicator), + gravity_prepared_test_utils::global_norm(right, communicator), + 100.0 * std::numeric_limits::epsilon()} + ); + + return gravity_prepared_test_utils::global_norm(difference, communicator) / scale; + } + + [[nodiscard]] + mean_field::operators::context::pressure_force::PressureForceDependencies make_dependencies() { + return { + .discretization = {.identity = 1201, .revision = 3}, + .enthalpy = {.identity = 1213, .revision = 5}, + .displacement = {.identity = 1217, .revision = 7} + }; + } + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto gravityGradientValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); + + constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.gradient_term + ); + + constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.poisson_term + ); + + constexpr auto densityResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + [[nodiscard]] + mean_field::operators::BarotropicEquilibriumLayout make_coupled_layout(const Maps &maps) { + const std::array valueSizes{ + maps.density.reduced_size(), maps.displacement.reduced_size(), maps.gravityFlux.reduced_size(), + maps.gravityPotential.reduced_size(), maps.enthalpy.reduced_size(), 1 + }; + + const std::array residualSizes{ + maps.gravityFlux.reduced_size(), maps.gravityPotential.reduced_size(), maps.density.reduced_size(), + maps.displacement.reduced_size(), maps.enthalpy.reduced_size(), 1 + }; + + return {valueSizes, residualSizes}; + } + + template + [[nodiscard]] + mfem::Vector copy_residual_block( + const mfem::Vector &action, + const mean_field::operators::BarotropicEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + mfem::Vector result(layout.size(block)); + + const int offset = layout.offset(block); + + for (int entry = 0; entry < result.Size(); ++entry) { + result(entry) = action(offset + entry); + } + + return result; + } +} // namespace prepared_pressure_force_test_utils + +TEST_CASE( + "Prepared Pressure Force Uses FieldDof Supported Dimensions And Owns Its Context", + tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::unit +) { + 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 prepared_pressure_force_test_utils::Maps maps(f); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState); + + REQUIRE(maps.enthalpy.reduced_size() < maps.enthalpy.full_size()); + + CHECK(maps.displacement.is_identity()); + + CHECK(preparedOperator.GetEnthalpySize() == maps.enthalpy.reduced_size()); + + CHECK(preparedOperator.GetDisplacementSize() == maps.displacement.reduced_size()); + + CHECK( + &preparedOperator.GetContext().GetPreparationStatistics() == &preparedOperator.GetContextPreparationStatistics() + ); +} + +TEST_CASE( + "Prepared Pressure Force Jacobian Matches Full Stateless Columns Through FieldDof Restriction", + tags::barotrope &tags::pressure &tags::prepared &tags::field &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()); + + const prepared_pressure_force_test_utils::Maps maps(f); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + const mfem::Vector enthalpy = + maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.47)); + + const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.69)); + + const mfem::Vector enthalpyDirection = + maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.73)); + + const mfem::Vector displacementDirection = + maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.83)); + + mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState); + + preparedOperator.Prepare( + {.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies() + ); + + const mfem::Vector enthalpyTrue = maps.enthalpy.scatter(enthalpy); + + const mfem::Vector displacementTrue = maps.displacement.scatter(displacement); + + const mfem::Vector enthalpyDirectionTrue = maps.enthalpy.scatter(enthalpyDirection); + + const mfem::Vector displacementDirectionTrue = maps.displacement.scatter(displacementDirection); + + mfem::Vector preparedEnthalpyAction; + mfem::Vector kernelEnthalpyActionTrue; + + preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, preparedEnthalpyAction); + + mean_field::operators::kernels::apply_pressure_force_enthalpy_action( + f, *f.domainMapperStateless, equationOfState, enthalpyTrue, enthalpyDirectionTrue, displacementTrue, + kernelEnthalpyActionTrue + ); + + const mfem::Vector kernelEnthalpyAction = maps.displacement.gather(kernelEnthalpyActionTrue); + + CHECK( + prepared_pressure_force_test_utils::relative_difference( + preparedEnthalpyAction, kernelEnthalpyAction, f.mesh->GetComm() + ) < 2.0e-12 + ); + + mfem::Vector preparedDisplacementAction; + mfem::Vector kernelDisplacementActionTrue; + + preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedDisplacementAction); + + mean_field::operators::kernels::apply_pressure_force_displacement_action( + f, *f.domainMapperStateless, equationOfState, enthalpyTrue, displacementDirectionTrue, displacementTrue, + kernelDisplacementActionTrue + ); + + const mfem::Vector kernelDisplacementAction = maps.displacement.gather(kernelDisplacementActionTrue); + + CHECK( + prepared_pressure_force_test_utils::relative_difference( + preparedDisplacementAction, kernelDisplacementAction, f.mesh->GetComm() + ) < 2.0e-12 + ); + + mfem::Vector fusedAction; + + preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, fusedAction); + + mfem::Vector expectedFusedAction(kernelEnthalpyAction); + + expectedFusedAction += kernelDisplacementAction; + + CHECK( + prepared_pressure_force_test_utils::relative_difference(fusedAction, expectedFusedAction, f.mesh->GetComm()) < + 2.0e-12 + ); +} + +TEST_CASE( + "Prepared Pressure Force MFEM Adapter Routes Reduced Coupled FieldDof Blocks", + tags::barotrope &tags::pressure &tags::prepared &tags::field &tags::integration &tags::jacobian + &tags::mfem_operators &tags::unit +) { + 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 prepared_pressure_force_test_utils::Maps maps(f); + + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + const mfem::Vector enthalpy = + maps.enthalpy.gather(prepared_pressure_force_test_utils::make_positive_enthalpy_true(f, 0.53)); + + const mfem::Vector displacement = maps.displacement.gather(gravity_prepared_test_utils::make_displacement(f, 0.71)); + + const mfem::Vector enthalpyDirection = + maps.enthalpy.gather(prepared_pressure_force_test_utils::make_enthalpy_direction_true(f, 0.89)); + + const mfem::Vector displacementDirection = + maps.displacement.gather(prepared_pressure_force_test_utils::make_displacement_direction_true(f, 0.97)); + + mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState); + + preparedOperator.Prepare( + {.enthalpy = enthalpy, .displacement = displacement}, prepared_pressure_force_test_utils::make_dependencies() + ); + + const mean_field::operators::BarotropicEquilibriumLayout layout = + prepared_pressure_force_test_utils::make_coupled_layout(maps); + + mean_field::operators::PreparedPressureForceJacobianOperator adapter(layout, preparedOperator); + + CHECK(layout.size(prepared_pressure_force_test_utils::enthalpyValue) == maps.enthalpy.reduced_size()); + + CHECK(layout.size(prepared_pressure_force_test_utils::densityValue) == maps.density.reduced_size()); + + mfem::BlockVector direction(layout.value_offsets()); + + direction = 0.0; + + /* + * Populate unrelated columns deliberately. + */ + direction.GetBlock(prepared_pressure_force_test_utils::densityValue) = 0.37; + + direction.GetBlock(prepared_pressure_force_test_utils::gravityGradientValue) = -0.41; + + direction.GetBlock(prepared_pressure_force_test_utils::gravityPotentialValue) = 0.59; + + direction.GetBlock(prepared_pressure_force_test_utils::barotropicConstantValue) = -0.73; + + direction.GetBlock(prepared_pressure_force_test_utils::displacementValue) = displacementDirection; + + direction.GetBlock(prepared_pressure_force_test_utils::enthalpyValue) = enthalpyDirection; + + mfem::Vector expectedDisplacementAction; + + preparedOperator.ApplyCompleteJacobianAction(enthalpyDirection, displacementDirection, expectedDisplacementAction); + + mfem::Vector action; + + adapter.Mult(direction, action); + + const mfem::Vector displacementResidualAction = prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::displacementResidual + ); + + CHECK( + prepared_pressure_force_test_utils::relative_difference( + displacementResidualAction, expectedDisplacementAction, f.mesh->GetComm() + ) < 2.0e-14 + ); + + CHECK( + prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::gravityGradientResidual + ) + .Norml2() == 0.0 + ); + + CHECK( + prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::gravityPotentialResidual + ) + .Norml2() == 0.0 + ); + + CHECK( + prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::densityResidual + ) + .Norml2() == 0.0 + ); + + CHECK( + prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::enthalpyResidual + ) + .Norml2() == 0.0 + ); + + CHECK( + prepared_pressure_force_test_utils::copy_residual_block( + action, layout, prepared_pressure_force_test_utils::massResidual + ) + .Norml2() == 0.0 + ); +} +TEST_CASE( + "Pressure Force Residual Converges To A Manufactured Analytic Force", + tags::barotrope &tags::pressure &tags::kernels &tags::integration &tags::convergence &tags::h_refinement + &tags::analytic_comparison &tags::accuracy +) { + constexpr int dimension = 3; + + constexpr std::array refinementLevels{0, 1}; + + constexpr double minimumObservedRate = 3.0; + constexpr double finestRelativeTolerance = 2.0e-3; + + constexpr double amplitude = 1.0; + constexpr double bumpSharpness = 0.25; + constexpr double supportRadiusFraction = 0.90; + + std::array relativeErrors{}; + + for (std::size_t levelIndex = 0; levelIndex < refinementLevels.size(); ++levelIndex) { + mean_field::utils::Args args = test_utils::setup_args(); + + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, refinementLevels[levelIndex]); + + REQUIRE(f.okay()); + REQUIRE(f.mesh->Dimension() == dimension); + REQUIRE(f.mesh->GetNE() > 0); + + const MPI_Comm communicator = f.mesh->GetComm(); + + constexpr double supportRadius = supportRadiusFraction * mean_field::utils::RADIUS; + + constexpr double supportRadiusSquared = supportRadius * supportRadius; + + auto analyticEnthalpyFunction = [supportRadiusSquared](const mfem::Vector &position) { + const double normalizedRadiusSquared = (position * position) / supportRadiusSquared; + + if (normalizedRadiusSquared >= 1.0) { + return 0.0; + } + + const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared; + + return amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary); + }; + + auto analyticPressureForceFunction = [supportRadiusSquared](const mfem::Vector &position, mfem::Vector &force) { + force.SetSize(dimension); + force = 0.0; + + const double normalizedRadiusSquared = (position * position) / supportRadiusSquared; + + if (normalizedRadiusSquared >= 1.0) { + return; + } + + const double distanceToSupportBoundary = 1.0 - normalizedRadiusSquared; + + const double enthalpy = + amplitude * std::exp(-bumpSharpness * normalizedRadiusSquared / distanceToSupportBoundary); + + const double pressureGradientScale = + -2.0 * bumpSharpness * std::pow(enthalpy, 4.0) / + (supportRadiusSquared * distanceToSupportBoundary * distanceToSupportBoundary); + + for (int component = 0; component < dimension; ++component) { + force(component) = pressureGradientScale * position(component); + } + }; + + mfem::FunctionCoefficient analyticEnthalpyCoefficient(analyticEnthalpyFunction); + + mfem::VectorFunctionCoefficient analyticPressureForceCoefficient(dimension, analyticPressureForceFunction); + + mfem::ParGridFunction discreteEnthalpyField(f.enthalpyFes.get()); + + discreteEnthalpyField.ProjectCoefficient(analyticEnthalpyCoefficient); + + mfem::Vector discreteEnthalpyTrue; + discreteEnthalpyField.GetTrueDofs(discreteEnthalpyTrue); + + mfem::Vector zeroDisplacement(f.displacementFes->GetTrueVSize()); + + zeroDisplacement = 0.0; + + const mean_field::eos::Polytrope barotrope(3.0, 0.25); + + mfem::Vector discreteResidual; + + mean_field::operators::kernels::apply_pressure_force_residual( + f, *f.domainMapperStateless, barotrope, discreteEnthalpyTrue, zeroDisplacement, discreteResidual + ); + + REQUIRE(discreteResidual.Size() == f.displacementFes->GetTrueVSize()); + + mfem::Array stellarMarker(f.mesh->attributes.Max()); + + stellarMarker = 0; + + const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); + + for (int attributeIndex = 0; attributeIndex < f.mesh->attributes.Size(); ++attributeIndex) { + const int attribute = f.mesh->attributes[attributeIndex]; + + if (attribute != vacuumAttribute) { + stellarMarker[attribute - 1] = 1; + } + } + + const mfem::Geometry::Type elementGeometry = f.displacementFes->GetFE(0)->GetGeomType(); + + for (int element = 1; element < f.mesh->GetNE(); ++element) { + REQUIRE(f.displacementFes->GetFE(element)->GetGeomType() == elementGeometry); + } + + const int referenceQuadratureOrder = 2 * f.displacementFes->GetMaxElementOrder() + 16; + + const mfem::IntegrationRule &referenceQuadrature = + mfem::IntRules.Get(elementGeometry, referenceQuadratureOrder); + + auto *analyticForceIntegrator = new mfem::VectorDomainLFIntegrator(analyticPressureForceCoefficient); + + analyticForceIntegrator->SetIntRule(&referenceQuadrature); + + mfem::ParLinearForm analyticForceLoad(f.displacementFes.get()); + + analyticForceLoad.AddDomainIntegrator(analyticForceIntegrator, stellarMarker); + + analyticForceLoad.Assemble(); + + std::unique_ptr analyticForceHypreVector(analyticForceLoad.ParallelAssemble()); + + REQUIRE(analyticForceHypreVector != nullptr); + + mfem::Vector analyticForceTrue(*analyticForceHypreVector); + + REQUIRE(analyticForceTrue.Size() == discreteResidual.Size()); + + const double analyticForceNorm = gravity_prepared_test_utils::global_norm(analyticForceTrue, communicator); + + REQUIRE(std::isfinite(analyticForceNorm)); + REQUIRE(analyticForceNorm > 0.0); + + mfem::Vector residualError(discreteResidual); + residualError -= analyticForceTrue; + + mfem::ParBilinearForm rieszForm(f.displacementFes.get()); + + rieszForm.AddDomainIntegrator(new mfem::VectorMassIntegrator()); + + rieszForm.AddDomainIntegrator(new mfem::VectorDiffusionIntegrator()); + + rieszForm.Assemble(); + rieszForm.Finalize(); + + std::unique_ptr rieszMatrix(rieszForm.ParallelAssemble()); + + REQUIRE(rieszMatrix != nullptr); + REQUIRE(rieszMatrix->Height() == discreteResidual.Size()); + REQUIRE(rieszMatrix->Width() == discreteResidual.Size()); + + mfem::HypreBoomerAMG rieszPreconditioner(*rieszMatrix); + + rieszPreconditioner.SetPrintLevel(0); + + mfem::CGSolver rieszSolver(communicator); + + rieszSolver.SetOperator(*rieszMatrix); + rieszSolver.SetPreconditioner(rieszPreconditioner); + rieszSolver.SetRelTol(1.0e-13); + rieszSolver.SetAbsTol(1.0e-15); + rieszSolver.SetMaxIter(5000); + rieszSolver.SetPrintLevel(1); + + auto calculateDualNorm = [&rieszSolver, communicator](const mfem::Vector &functional) { + mfem::Vector rieszRepresentative(functional.Size()); + + rieszRepresentative = 0.0; + + rieszSolver.Mult(functional, rieszRepresentative); + + MFEM_VERIFY( + rieszSolver.GetConverged(), "The pressure-force convergence-test Riesz solve " + "did not converge." + ); + + const double dualNormSquared = + gravity_prepared_test_utils::global_dot(functional, rieszRepresentative, communicator); + + MFEM_VERIFY(std::isfinite(dualNormSquared), "The pressure-force dual norm is not finite."); + + MFEM_VERIFY( + dualNormSquared >= -100.0 * std::numeric_limits::epsilon(), + "The pressure-force Riesz operator produced a " + "negative dual norm." + ); + + return std::sqrt(std::max(dualNormSquared, 0.0)); + }; + + const double errorDualNorm = calculateDualNorm(residualError); + + const double analyticDualNorm = calculateDualNorm(analyticForceTrue); + + REQUIRE(std::isfinite(errorDualNorm)); + REQUIRE(std::isfinite(analyticDualNorm)); + REQUIRE(errorDualNorm > 0.0); + REQUIRE(analyticDualNorm > 0.0); + + relativeErrors[levelIndex] = errorDualNorm / analyticDualNorm; + + INFO("Pressure-force refinement level = " << refinementLevels[levelIndex]); + + INFO("Pressure-force true DOFs = " << f.displacementFes->GlobalTrueVSize()); + + INFO("Pressure-force relative dual error = " << relativeErrors[levelIndex]); + } + + for (const double relativeError : relativeErrors) { + REQUIRE(std::isfinite(relativeError)); + REQUIRE(relativeError > 0.0); + } + + static_assert(refinementLevels.size() == 2, "This reduced convergence test expects exactly two refinement levels."); + + const double observedRate = std::log(relativeErrors[0] / relativeErrors[1]) / std::log(2.0); + + INFO("Level 0 pressure-force relative dual error = " << relativeErrors[0]); + + INFO("Level 1 pressure-force relative dual error = " << relativeErrors[1]); + + INFO("Level 0 to 1 pressure-force convergence rate = " << observedRate); + + CHECK(relativeErrors[1] < relativeErrors[0]); + + CHECK(observedRate > minimumObservedRate); + + CHECK(relativeErrors[1] < finestRelativeTolerance); +} diff --git a/tests/operators/prepared_rotation_displacement_force.cpp b/tests/operators/prepared_rotation_displacement_force.cpp new file mode 100644 index 0000000..4169844 --- /dev/null +++ b/tests/operators/prepared_rotation_displacement_force.cpp @@ -0,0 +1,609 @@ +#include +#include +#include +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace rotational_displacement_force_test_utils { + using CoupledForm = mean_field::utils::blocks::barotropic_equilibrium_form; + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto gravityGradientValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); + + constexpr auto gravityPotentialValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); + + constexpr auto enthalpyValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto barotropicConstantValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + constexpr auto gravityGradientResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.gradient_term + ); + + constexpr auto gravityPotentialResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::gravity_field.poisson_term + ); + + constexpr auto densityResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::density_field.mass_term); + + constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + + constexpr auto enthalpyResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::enthalpy_field.specific_term + ); + + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + [[nodiscard]] mean_field::operators::RotationalDisplacementForceLayout make_layout(const mean_field::fem::FEM &f) { + const std::array valueSizes{ + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + const std::array residualSizes{ + f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize(), f.densityFes->GetTrueVSize(), + f.displacementFes->GetTrueVSize(), f.enthalpyFes->GetTrueVSize(), 1 + }; + + return {valueSizes, residualSizes}; + } + + [[nodiscard]] mfem::Vector make_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction densityField(f.densityFes.get()); + + mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) { + return 0.88 + 0.06 * std::sin(0.7 * position(0) + phase) + 0.04 * std::cos(0.6 * position(1) - phase) + + 0.025 * position(2) * position(2); + }); + + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector make_density_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction densityField(f.densityFes.get()); + + mfem::FunctionCoefficient densityCoefficient([phase](const mfem::Vector &position) { + return 0.17 * std::sin(0.9 * position(0) + phase) - 0.12 * std::cos(0.8 * position(1) - phase) + + 0.07 * position(2); + }); + + densityField.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector make_displacement_direction(const mean_field::fem::FEM &f) { + mfem::Vector direction = gravity_prepared_test_utils::make_displacement(f, 0.91); + + const mfem::Vector second = gravity_prepared_test_utils::make_displacement(f, 0.27); + + direction -= second; + return direction; + } + + [[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) { + mfem::Vector angularVelocity(3); + angularVelocity(0) = scale * 0.17; + angularVelocity(1) = scale * -0.09; + angularVelocity(2) = scale * 0.62; + + mfem::Vector center(3); + center(0) = 0.04; + center(1) = -0.03; + center(2) = 0.02; + + return mean_field::physics::RigidRotation(angularVelocity, center); + } + + [[nodiscard]] mean_field::operators::context::rotational_displacement_force::RotationalDisplacementForceDependencies + make_dependencies() { + return { + .discretization = {.identity = 211, .revision = 3}, + .density = {.identity = 223, .revision = 5}, + .displacement = {.identity = 227, .revision = 7}, + .rotation = {.identity = 229, .revision = 11} + }; + } + + [[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) { + mfem::ParGridFunction densityField(f.densityFes.get()); + densityField = 0.0; + + const int vacuumAttribute = f.domainMapperStateless->GetVacuumElementAttribute(); + + mfem::Array densityDofs; + int localVacuumElements = 0; + + for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) { + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elementId); + + REQUIRE(transformation != nullptr); + + if (transformation->Attribute != vacuumAttribute) { + continue; + } + + f.densityFes->GetElementDofs(elementId, densityDofs); + + mfem::Vector elementDensity(densityDofs.Size()); + elementDensity = 1.0; + densityField.SetSubVector(densityDofs, elementDensity); + ++localVacuumElements; + } + + int globalVacuumElements = 0; + + MPI_Allreduce(&localVacuumElements, &globalVacuumElements, 1, MPI_INT, MPI_SUM, f.mesh->GetComm()); + + REQUIRE(globalVacuumElements > 0); + + mfem::Vector densityTrue; + densityField.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] double global_norm( + const mfem::Vector &vector, + MPI_Comm communicator + ) { + const double localSquaredNorm = vector * vector; + double globalSquaredNorm = 0.0; + + MPI_Allreduce(&localSquaredNorm, &globalSquaredNorm, 1, MPI_DOUBLE, MPI_SUM, communicator); + + return std::sqrt(globalSquaredNorm); + } + + [[nodiscard]] double global_dot( + const mfem::Vector &left, + const mfem::Vector &right, + MPI_Comm communicator + ) { + REQUIRE(left.Size() == right.Size()); + + const double localDot = left * right; + double globalDot = 0.0; + + MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator); + + return globalDot; + } + + [[nodiscard]] double relative_difference( + const mfem::Vector &computed, + const mfem::Vector &reference, + MPI_Comm communicator + ) { + REQUIRE(computed.Size() == reference.Size()); + + mfem::Vector difference(computed); + difference -= reference; + + return global_norm(difference, communicator) / + std::max(global_norm(reference, communicator), std::numeric_limits::epsilon()); + } + + [[nodiscard]] mfem::Vector centered_difference( + const mean_field::fem::FEM &f, + const mean_field::physics::RigidRotation &rotation, + const mfem::Vector &baseDensity, + const mfem::Vector &densityDirection, + const mfem::Vector &baseDisplacement, + const mfem::Vector &displacementDirection, + const double step + ) { + mfem::Vector plusDensity(baseDensity); + plusDensity.Add(step, densityDirection); + + mfem::Vector minusDensity(baseDensity); + minusDensity.Add(-step, densityDirection); + + mfem::Vector plusDisplacement(baseDisplacement); + plusDisplacement.Add(step, displacementDirection); + + mfem::Vector minusDisplacement(baseDisplacement); + minusDisplacement.Add(-step, displacementDirection); + + mfem::Vector plusResidual; + mfem::Vector minusResidual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, plusDensity, plusDisplacement, plusResidual + ); + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, minusDensity, minusDisplacement, minusResidual + ); + + plusResidual -= minusResidual; + plusResidual /= 2.0 * step; + return plusResidual; + } + + template + [[nodiscard]] mfem::Vector copy_residual_block( + const mfem::Vector &action, + const mean_field::operators::RotationalDisplacementForceLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + mfem::Vector result(layout.size(block)); + const int offset = layout.offset(block); + + for (int entry = 0; entry < result.Size(); ++entry) { + result(entry) = action(offset + entry); + } + + return result; + } +} // namespace rotational_displacement_force_test_utils + +TEST_CASE( + "Rotational Displacement Force Query Includes Density Test And Linear " + "Position", + tags::centrifugal &tags::quadrature &tags::unit +) { + using DisplacementField = mean_field::field::Field; + + constexpr int geometryWeightOrder = 4; + + constexpr mean_field::quadrature::Query query = + DisplacementField::make_query( + mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, std::array{1}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general + ); + + /* density: 2, displacement test: 3, position: 1, geometry: 4 */ + constexpr int expectedBaseOrder = 2 + 3 + 1 + 4; + + STATIC_REQUIRE(query.term == mean_field::quadrature::Term::centrifugal); + STATIC_REQUIRE(query.domain == mean_field::utils::DOMAINS::STELLAR); + STATIC_REQUIRE(query.mapping == mean_field::quadrature::MappingKind::general); + STATIC_REQUIRE(query.base_order.has_value()); + STATIC_REQUIRE(*query.base_order == expectedBaseOrder); +} + +TEST_CASE( + "Rotational Displacement Force Uses Negative Rotation-Potential " + "Gradient And Excludes Vacuum", + tags::centrifugal &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()); + + const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.31); + + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, density, displacement, residual + ); + + mfem::ParGridFunction gradientTestField(f.displacementFes.get()); + + auto gradientFunction = [&rotation](const mfem::Vector &position, mfem::Vector &value) { + rotation.potential_gradient(position, value); + }; + + mfem::VectorFunctionCoefficient gradientCoefficient(3, gradientFunction); + + gradientTestField.ProjectCoefficient(gradientCoefficient); + + mfem::Vector gradientTestDirection; + gradientTestField.GetTrueDofs(gradientTestDirection); + + const double signedWork = + rotational_displacement_force_test_utils::global_dot(residual, gradientTestDirection, f.mesh->GetComm()); + + INFO("Rotation-force work against grad(Psi) = " << signedWork); + CHECK(signedWork < 0.0); + + const mfem::Vector vacuumDensity = rotational_displacement_force_test_utils::make_vacuum_only_density(f); + + mfem::Vector vacuumResidual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, vacuumDensity, displacement, vacuumResidual + ); + + CHECK(rotational_displacement_force_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0); + + mfem::Vector zeroAngularVelocity(3); + mfem::Vector zeroCenter(3); + zeroAngularVelocity = 0.0; + zeroCenter = 0.0; + + const mean_field::physics::RigidRotation zeroRotation(zeroAngularVelocity, zeroCenter); + + mfem::Vector zeroRotationResidual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, zeroRotation, density, displacement, zeroRotationResidual + ); + + CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0); +} + +TEST_CASE( + "Prepared Rotational Displacement Force Reprepares Selectively", + tags::centrifugal &tags::prepared &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()); + + mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.37); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.53); + + mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.81); + + auto dependencies = rotational_displacement_force_test_utils::make_dependencies(); + + mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless); + + const auto initialReport = + preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation); + + REQUIRE(initialReport.DidAnyWork()); + REQUIRE(initialReport.updatedRotation); + REQUIRE(initialReport.preparedResidual); + REQUIRE(preparedOperator.IsPrepared()); + + mfem::Vector preparedResidual; + mfem::Vector kernelResidual; + + preparedOperator.BuildResidual(preparedResidual); + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, density, displacement, kernelResidual + ); + + CHECK( + rotational_displacement_force_test_utils::relative_difference( + preparedResidual, kernelResidual, f.mesh->GetComm() + ) < 2.0e-12 + ); + + CHECK_FALSE(preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation) + .DidAnyWork()); + + density = rotational_displacement_force_test_utils::make_density(f, 0.79); + + ++dependencies.density.revision; + + const auto densityReport = + preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation); + + CHECK(densityReport.preparedResidual); + CHECK_FALSE(densityReport.updatedRotation); + + rotation = rotational_displacement_force_test_utils::make_rotation(1.23); + + ++dependencies.rotation.revision; + + const auto rotationReport = + preparedOperator.Prepare({.density = density, .displacement = displacement}, dependencies, rotation); + + CHECK(rotationReport.updatedRotation); + CHECK(rotationReport.preparedResidual); + CHECK(preparedOperator.GetResidualPreparationCount() == 3); + CHECK(preparedOperator.GetResidualApplicationCount() == 1); +} + +TEST_CASE( + "Rotational Displacement Force Jacobian Matches Both Columns And " + "Centered Differences", + tags::centrifugal &tags::prepared &tags::jacobian &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()); + + const mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.43); + + const mfem::Vector densityDirection = rotational_displacement_force_test_utils::make_density_direction(f, 0.59); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.61); + + const mfem::Vector displacementDirection = rotational_displacement_force_test_utils::make_displacement_direction(f); + + const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.93); + + mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless); + + preparedOperator.Prepare( + {.density = density, .displacement = displacement}, + rotational_displacement_force_test_utils::make_dependencies(), rotation + ); + + mfem::Vector densityAction; + mfem::Vector displacementAction; + mfem::Vector completeAction; + + preparedOperator.ApplyDensityJacobianAction(densityDirection, densityAction); + + preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, displacementAction); + + preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction); + + mfem::Vector summedColumns(densityAction); + summedColumns += displacementAction; + + CHECK( + rotational_displacement_force_test_utils::relative_difference( + completeAction, summedColumns, f.mesh->GetComm() + ) < 2.0e-12 + ); + + mfem::Vector zeroDensity(densityDirection.Size()); + mfem::Vector zeroDisplacement(displacementDirection.Size()); + zeroDensity = 0.0; + zeroDisplacement = 0.0; + + constexpr double step = 1.0e-5; + + const mfem::Vector densityDifference = rotational_displacement_force_test_utils::centered_difference( + f, rotation, density, densityDirection, displacement, zeroDisplacement, step + ); + + const mfem::Vector displacementDifference = rotational_displacement_force_test_utils::centered_difference( + f, rotation, density, zeroDensity, displacement, displacementDirection, step + ); + + const mfem::Vector completeDifference = rotational_displacement_force_test_utils::centered_difference( + f, rotation, density, densityDirection, displacement, displacementDirection, step + ); + + const double densityError = rotational_displacement_force_test_utils::relative_difference( + densityAction, densityDifference, f.mesh->GetComm() + ); + + const double displacementError = rotational_displacement_force_test_utils::relative_difference( + displacementAction, displacementDifference, f.mesh->GetComm() + ); + + const double completeError = rotational_displacement_force_test_utils::relative_difference( + completeAction, completeDifference, f.mesh->GetComm() + ); + + INFO("Density-column centered-difference error = " << densityError); + INFO("Displacement-column centered-difference error = " << displacementError); + INFO("Complete centered-difference error = " << completeError); + + CHECK(densityError < 2.0e-9); + CHECK(displacementError < 3.0e-8); + CHECK(completeError < 4.0e-8); +} + +TEST_CASE( + "Prepared Rotational Displacement Force MFEM Adapter Routes Only R-d", + tags::centrifugal &tags::prepared &tags::mfem_operators &tags::unit +) { + 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 mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.47); + + const mfem::Vector densityDirection = rotational_displacement_force_test_utils::make_density_direction(f, 0.63); + + const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.57); + + const mfem::Vector displacementDirection = rotational_displacement_force_test_utils::make_displacement_direction(f); + + const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.87); + + mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless); + + preparedOperator.Prepare( + {.density = density, .displacement = displacement}, + rotational_displacement_force_test_utils::make_dependencies(), rotation + ); + + const auto layout = rotational_displacement_force_test_utils::make_layout(f); + + mean_field::operators::PreparedRotationalDisplacementForceJacobianOperator adapter(layout, preparedOperator); + + mfem::BlockVector direction(layout.value_offsets()); + direction = 0.0; + + direction.GetBlock(rotational_displacement_force_test_utils::densityValue) = densityDirection; + + direction.GetBlock(rotational_displacement_force_test_utils::displacementValue) = displacementDirection; + + direction.GetBlock(rotational_displacement_force_test_utils::gravityGradientValue) = 0.23; + + direction.GetBlock(rotational_displacement_force_test_utils::gravityPotentialValue) = -0.31; + + direction.GetBlock(rotational_displacement_force_test_utils::enthalpyValue) = 0.37; + + direction.GetBlock(rotational_displacement_force_test_utils::barotropicConstantValue) = -0.41; + + mfem::Vector action; + adapter.Mult(direction, action); + + mfem::Vector expectedDisplacementAction; + + preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, expectedDisplacementAction); + + const mfem::Vector actualDisplacementAction = rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::displacementResidual + ); + + CHECK( + rotational_displacement_force_test_utils::relative_difference( + actualDisplacementAction, expectedDisplacementAction, f.mesh->GetComm() + ) < 2.0e-12 + ); + + const std::array zeroRows{ + rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::gravityGradientResidual + ), + rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::gravityPotentialResidual + ), + rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::densityResidual + ), + rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::enthalpyResidual + ), + rotational_displacement_force_test_utils::copy_residual_block( + action, layout, rotational_displacement_force_test_utils::massResidual + ) + }; + + for (const mfem::Vector &row : zeroRows) { + CHECK(rotational_displacement_force_test_utils::global_norm(row, f.mesh->GetComm()) == 0.0); + } +} diff --git a/tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp b/tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp new file mode 100644 index 0000000..07ffb00 --- /dev/null +++ b/tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp @@ -0,0 +1,303 @@ +#include +#include +#include +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace rotational_displacement_force_affine_deformation_test_utils { + [[nodiscard]] double global_dot( + const mfem::Vector &left, + const mfem::Vector &right, + MPI_Comm communicator + ) { + REQUIRE(left.Size() == right.Size()); + + const double localDot = left * right; + double globalDot = 0.0; + + MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator); + + return globalDot; + } + + [[nodiscard]] double relative_error( + const double computed, + const double expected + ) { + return std::abs(computed - expected) / std::max(std::abs(expected), std::numeric_limits::epsilon()); + } + + [[nodiscard]] mfem::Vector project_constant_density( + const mean_field::fem::FEM &f, + const double densityValue + ) { + mfem::ParGridFunction density(f.densityFes.get()); + mfem::ConstantCoefficient coefficient(densityValue); + density.ProjectCoefficient(coefficient); + + mfem::Vector densityTrue; + density.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector project_affine_vector( + const mean_field::fem::FEM &f, + const mfem::DenseMatrix &linearMap, + const mfem::Vector &offset + ) { + REQUIRE(linearMap.Height() == 3); + REQUIRE(linearMap.Width() == 3); + REQUIRE(offset.Size() == 3); + + auto affineFunction = [&linearMap, &offset](const mfem::Vector &referencePosition, mfem::Vector &value) { + value.SetSize(3); + linearMap.Mult(referencePosition, value); + value += offset; + }; + + mfem::ParGridFunction field(f.displacementFes.get()); + mfem::VectorFunctionCoefficient coefficient(3, affineFunction); + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } + + [[nodiscard]] mfem::DenseMatrix make_deformation() { + mfem::DenseMatrix deformation(3); + deformation = 0.0; + + deformation(0, 0) = 1.08; + deformation(0, 1) = 0.06; + deformation(0, 2) = -0.03; + deformation(1, 1) = 0.96; + deformation(1, 2) = 0.04; + deformation(2, 2) = 1.0 / (1.08 * 0.96); + + return deformation; + } + + [[nodiscard]] mfem::DenseMatrix make_displacement_gradient(const mfem::DenseMatrix &deformation) { + mfem::DenseMatrix displacementGradient(deformation); + + for (int component = 0; component < 3; ++component) { + displacementGradient(component, component) -= 1.0; + } + + return displacementGradient; + } + + [[nodiscard]] mfem::DenseMatrix make_rotation_potential_hessian(const mfem::Vector &angularVelocity) { + REQUIRE(angularVelocity.Size() == 3); + + const double angularSpeedSquared = angularVelocity * angularVelocity; + mfem::DenseMatrix hessian(3); + + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + hessian(row, column) = + (row == column ? angularSpeedSquared : 0.0) - angularVelocity(row) * angularVelocity(column); + } + } + + return hessian; + } + + [[nodiscard]] mfem::DenseMatrix multiply( + const mfem::DenseMatrix &left, + const mfem::DenseMatrix &right + ) { + REQUIRE(left.Width() == right.Height()); + + mfem::DenseMatrix product(left.Height(), right.Width()); + product = 0.0; + + for (int row = 0; row < product.Height(); ++row) { + for (int column = 0; column < product.Width(); ++column) { + for (int inner = 0; inner < left.Width(); ++inner) { + product(row, column) += left(row, inner) * right(inner, column); + } + } + } + + return product; + } + + [[nodiscard]] double frobenius_product( + const mfem::DenseMatrix &left, + const mfem::DenseMatrix &right + ) { + REQUIRE(left.Height() == right.Height()); + REQUIRE(left.Width() == right.Width()); + + double product = 0.0; + + for (int row = 0; row < left.Height(); ++row) { + for (int column = 0; column < left.Width(); ++column) { + product += left(row, column) * right(row, column); + } + } + + return product; + } +} // namespace rotational_displacement_force_affine_deformation_test_utils + +TEST_CASE( + "Rotational Displacement Force Matches A Nontrivially Deformed " + "Homogeneous Ellipsoid", + tags::centrifugal &tags::analytic_comparison &tags::accuracy &tags::geometry +) { + 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 double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; + const double referenceVolume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0; + + const mfem::DenseMatrix deformation = + rotational_displacement_force_affine_deformation_test_utils::make_deformation(); + + const double deformationDeterminant = deformation.Det(); + + REQUIRE(deformationDeterminant > 0.0); + REQUIRE(std::abs(deformationDeterminant - 1.0) < 2.0e-14); + + mfem::Vector deformationOffset(3); + deformationOffset(0) = 0.031; + deformationOffset(1) = -0.024; + deformationOffset(2) = 0.018; + + const mfem::DenseMatrix displacementGradient = + rotational_displacement_force_affine_deformation_test_utils::make_displacement_gradient(deformation); + + const mfem::Vector displacement = + rotational_displacement_force_affine_deformation_test_utils::project_affine_vector( + f, displacementGradient, deformationOffset + ); + + const double densityValue = mass / (deformationDeterminant * referenceVolume); + + const mfem::Vector density = + rotational_displacement_force_affine_deformation_test_utils::project_constant_density(f, densityValue); + + mfem::Vector angularVelocity(3); + angularVelocity(0) = 0.17; + angularVelocity(1) = -0.12; + angularVelocity(2) = 0.43; + + /* + * Put the rotation center at the mapped ellipsoid's center. The affine + * translation is therefore present in the geometry, but cancels from + * x - x_c in the exact centrifugal acceleration. + */ + const mfem::Vector rotationCenter(deformationOffset); + + const mean_field::physics::RigidRotation rotation(angularVelocity, rotationCenter); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, density, displacement, residual + ); + + const mfem::DenseMatrix rotationPotentialHessian = + rotational_displacement_force_affine_deformation_test_utils::make_rotation_potential_hessian(angularVelocity); + + const mfem::DenseMatrix forceMomentTensor = + rotational_displacement_force_affine_deformation_test_utils::multiply(rotationPotentialHessian, deformation); + + const double angularSpeedSquared = angularVelocity * angularVelocity; + const double momentScale = mass * radius * radius * angularSpeedSquared / 5.0; + + REQUIRE(momentScale > 0.0); + + mfem::Vector zeroOffset(3); + zeroOffset = 0.0; + + /* + * For X in a homogeneous reference sphere and the affine map x = A X+b, + * + * integral X_i X_j dM = (M R^2 / 5) delta_ij. + * + * With S = |Omega|^2 I - Omega Omega^T and the affine probe + * w = E_(row,column) X, the exact residual action is + * + * R_rot(w) = -(M R^2 / 5) (S A)_(row,column). + * + * Checking all nine probes compares the complete analytic second-moment + * response tensor, including the shear and oblique-axis couplings. + */ + for (int row = 0; row < 3; ++row) { + for (int column = 0; column < 3; ++column) { + mfem::DenseMatrix probe(3); + probe = 0.0; + probe(row, column) = 1.0; + + const mfem::Vector probeDirection = + rotational_displacement_force_affine_deformation_test_utils::project_affine_vector( + f, probe, zeroOffset + ); + + const double computedAction = rotational_displacement_force_affine_deformation_test_utils::global_dot( + residual, probeDirection, f.mesh->GetComm() + ); + + const double expectedAction = -mass * radius * radius / 5.0 * forceMomentTensor(row, column); + + const double normalizedAbsoluteError = std::abs(computedAction - expectedAction) / momentScale; + + INFO("Affine probe row = " << row); + INFO("Affine probe column = " << column); + INFO("Computed affine-probe action = " << computedAction); + INFO("Analytic affine-probe action = " << expectedAction); + INFO("Normalized affine-probe absolute error = " << normalizedAbsoluteError); + + CHECK(normalizedAbsoluteError < 2.5e-5); + } + } + + /* + * The physical dilation about the rotation center is w = x-x_c = A X. + * Its exact work is the deformed rotational virial, + * + * R_rot(x-x_c) = -(M R^2 / 5) tr(A^T S A) = -2T. + */ + const mfem::Vector dilationDirection = + rotational_displacement_force_affine_deformation_test_utils::project_affine_vector(f, deformation, zeroOffset); + + const double computedVirial = rotational_displacement_force_affine_deformation_test_utils::global_dot( + residual, dilationDirection, f.mesh->GetComm() + ); + + const double expectedVirial = + -mass * radius * radius / 5.0 * + rotational_displacement_force_affine_deformation_test_utils::frobenius_product(deformation, forceMomentTensor); + + const double virialRelativeError = + rotational_displacement_force_affine_deformation_test_utils::relative_error(computedVirial, expectedVirial); + + const double sphericalVirial = -(2.0 / 5.0) * mass * angularSpeedSquared * radius * radius; + + const double deformationSignal = std::abs(expectedVirial - sphericalVirial) / std::abs(sphericalVirial); + + INFO("Deformation determinant = " << deformationDeterminant); + INFO("Computed deformed rotational virial = " << computedVirial); + INFO("Analytic deformed rotational virial = " << expectedVirial); + INFO("Spherical rotational virial = " << sphericalVirial); + INFO("Relative deformation signal = " << deformationSignal); + INFO("Deformed virial relative error = " << virialRelativeError); + + CHECK(computedVirial < 0.0); + CHECK(deformationSignal > 5.0e-2); + CHECK(virialRelativeError < 2.5e-5); +} diff --git a/tests/operators/prepared_rotation_displacement_force_analytic.cpp b/tests/operators/prepared_rotation_displacement_force_analytic.cpp new file mode 100644 index 0000000..afa238c --- /dev/null +++ b/tests/operators/prepared_rotation_displacement_force_analytic.cpp @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include + +#include + +#include + +import mean_field; +import test_helpers; + +namespace rotational_displacement_force_analytic_test_utils { + [[nodiscard]] double global_dot( + const mfem::Vector &left, + const mfem::Vector &right, + MPI_Comm communicator + ) { + REQUIRE(left.Size() == right.Size()); + + const double localDot = left * right; + double globalDot = 0.0; + + MPI_Allreduce(&localDot, &globalDot, 1, MPI_DOUBLE, MPI_SUM, communicator); + + return globalDot; + } + + [[nodiscard]] double relative_error( + const double computed, + const double expected + ) { + return std::abs(computed - expected) / std::max(std::abs(expected), std::numeric_limits::epsilon()); + } + + [[nodiscard]] mfem::Vector project_constant_density( + const mean_field::fem::FEM &f, + const double densityValue + ) { + mfem::ParGridFunction density(f.densityFes.get()); + mfem::ConstantCoefficient densityCoefficient(densityValue); + density.ProjectCoefficient(densityCoefficient); + + mfem::Vector densityTrue; + density.GetTrueDofs(densityTrue); + return densityTrue; + } + + [[nodiscard]] mfem::Vector project_vector_function( + const mean_field::fem::FEM &f, + const std::function &function + ) { + mfem::ParGridFunction field(f.displacementFes.get()); + mfem::VectorFunctionCoefficient coefficient(3, function); + field.ProjectCoefficient(coefficient); + + mfem::Vector trueDofs; + field.GetTrueDofs(trueDofs); + return trueDofs; + } +} // namespace rotational_displacement_force_analytic_test_utils + +TEST_CASE( + "Rigid Rotation Gradient And Hessian Action Match The Analytic " + "Potential", + tags::centrifugal &tags::unit &tags::accuracy +) { + mfem::Vector angularVelocity(3); + angularVelocity(0) = 0.23; + angularVelocity(1) = -0.31; + angularVelocity(2) = 0.67; + + mfem::Vector center(3); + center(0) = 0.11; + center(1) = -0.07; + center(2) = 0.05; + + const mean_field::physics::RigidRotation rotation(angularVelocity, center); + + mfem::Vector position(3); + position(0) = 0.41; + position(1) = -0.29; + position(2) = 0.37; + + mfem::Vector direction(3); + direction(0) = -0.17; + direction(1) = 0.23; + direction(2) = 0.13; + + mfem::Vector gradient; + mfem::Vector hessianAction; + + rotation.potential_gradient(position, gradient); + + rotation.potential_gradient_directional_derivative(direction, hessianAction); + + const double directionalDerivative = rotation.potential_directional_derivative(position, direction); + + CHECK( + rotational_displacement_force_analytic_test_utils::relative_error(gradient * direction, directionalDerivative) < + 2.0e-15 + ); + + constexpr double step = 1.0e-6; + + mfem::Vector plusPosition(position); + plusPosition.Add(step, direction); + + mfem::Vector minusPosition(position); + minusPosition.Add(-step, direction); + + mfem::Vector plusGradient; + mfem::Vector minusGradient; + + rotation.potential_gradient(plusPosition, plusGradient); + rotation.potential_gradient(minusPosition, minusGradient); + + plusGradient -= minusGradient; + plusGradient /= 2.0 * step; + + plusGradient -= hessianAction; + + CHECK(plusGradient.Norml2() < 2.0e-10); +} + +TEST_CASE( + "Rotational Displacement Force Reproduces The Homogeneous Sphere " + "Rotational Virial", + tags::centrifugal &tags::analytic_comparison &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()); + + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; + const double volume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0; + const double densityValue = mass / volume; + const double angularSpeed = 0.73; + + const mfem::Vector density = + rotational_displacement_force_analytic_test_utils::project_constant_density(f, densityValue); + + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + mfem::Vector angularVelocity(3); + mfem::Vector center(3); + angularVelocity = 0.0; + center = 0.0; + angularVelocity(2) = angularSpeed; + + const mean_field::physics::RigidRotation rotation(angularVelocity, center); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, density, displacement, residual + ); + + const mfem::Vector dilationDirection = rotational_displacement_force_analytic_test_utils::project_vector_function( + f, [](const mfem::Vector &position, mfem::Vector &value) { value = position; } + ); + + const double computedWork = + rotational_displacement_force_analytic_test_utils::global_dot(residual, dilationDirection, f.mesh->GetComm()); + + const double expectedWork = -(2.0 / 5.0) * mass * angularSpeed * angularSpeed * radius * radius; + + const double relativeError = + rotational_displacement_force_analytic_test_utils::relative_error(computedWork, expectedWork); + + INFO("Computed rotational virial work = " << computedWork); + INFO("Analytic rotational virial work = " << expectedWork); + INFO("Rotational virial relative error = " << relativeError); + + CHECK(computedWork < 0.0); + CHECK(relativeError < 1.0e-5); +} + +TEST_CASE( + "Rotational Displacement Force Matches The Analytic Off-Axis " + "Resultant", + tags::centrifugal &tags::analytic_comparison &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()); + + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; + const double volume = 4.0 * std::numbers::pi * radius * radius * radius / 3.0; + const double densityValue = mass / volume; + const double angularSpeed = 0.61; + + const mfem::Vector density = + rotational_displacement_force_analytic_test_utils::project_constant_density(f, densityValue); + + mfem::Vector displacement(f.displacementFes->GetTrueVSize()); + displacement = 0.0; + + mfem::Vector angularVelocity(3); + mfem::Vector center(3); + angularVelocity = 0.0; + center = 0.0; + angularVelocity(2) = angularSpeed; + center(0) = 0.13; + center(1) = -0.08; + + const mean_field::physics::RigidRotation rotation(angularVelocity, center); + + mfem::Vector residual; + + mean_field::operators::kernels::apply_rotational_displacement_force_residual( + f, *f.domainMapperStateless, rotation, density, displacement, residual + ); + + for (int component = 0; component < 3; ++component) { + const mfem::Vector translationDirection = + rotational_displacement_force_analytic_test_utils::project_vector_function( + f, [component](const mfem::Vector &, mfem::Vector &value) { + value.SetSize(3); + value = 0.0; + value(component) = 1.0; + } + ); + + const double computedResultant = rotational_displacement_force_analytic_test_utils::global_dot( + residual, translationDirection, f.mesh->GetComm() + ); + + const double expectedResultant = component < 2 ? mass * angularSpeed * angularSpeed * center(component) : 0.0; + + INFO("Resultant component = " << component); + INFO("Computed resultant = " << computedResultant); + INFO("Expected resultant = " << expectedResultant); + + if (component < 2) { + CHECK( + rotational_displacement_force_analytic_test_utils::relative_error( + computedResultant, expectedResultant + ) < 1.0e-5 + ); + } else { + CHECK(std::abs(computedResultant) < 1.0e-10); + } + } +} diff --git a/tests/operators/prepared_stellar_equilibrium.cpp b/tests/operators/prepared_stellar_equilibrium.cpp new file mode 100644 index 0000000..3559b07 --- /dev/null +++ b/tests/operators/prepared_stellar_equilibrium.cpp @@ -0,0 +1,2365 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +import mean_field; +import test_helpers; + +namespace stellar_equilibrium_test_utils { + using Form = mean_field::utils::blocks::barotropic_equilibrium_form; + using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; + + namespace field = mean_field::field; + + struct FieldMaps final { + field::FieldDofMap density; + field::FieldDofMap displacement; + field::FieldDofMap gravityFlux; + field::FieldDofMap gravityPotential; + field::FieldDofMap enthalpy; + + explicit FieldMaps(const mean_field::fem::FEM &f) + : density( + field::make_field_dof_map< + field::Density, + DomainSchema>(*f.densityFes) + ), + displacement( + field::make_field_dof_map< + field::Displacement, + DomainSchema>(*f.displacementFes) + ), + gravityFlux( + field::make_field_dof_map< + field::Gravity, + DomainSchema>(*f.gravityFluxFes) + ), + gravityPotential( + field::make_field_dof_map< + field::Gravity, + DomainSchema>(*f.gravityPotentialFes) + ), + enthalpy( + field::make_field_dof_map< + field::Enthalpy, + DomainSchema>(*f.enthalpyFes) + ) { + } + }; + + constexpr auto densityValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::density_field.mass_term); + constexpr auto displacementValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::displacement_field.geometry_term); + constexpr auto gravityGradientValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::gravity_field.poisson_term); + constexpr auto enthalpyValue = + mean_field::utils::blocks::get_value_block(mean_field::utils::blocks::enthalpy_field.specific_term); + constexpr auto bernoulliValue = mean_field::utils::blocks::get_value_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + constexpr auto gravityGradientResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::gravity_field.gradient_term); + constexpr auto gravityPotentialResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::gravity_field.poisson_term); + constexpr auto densityResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::density_field.mass_term); + constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::displacement_field.geometry_term + ); + constexpr auto enthalpyResidual = + mean_field::utils::blocks::get_residual_block(mean_field::utils::blocks::enthalpy_field.specific_term); + constexpr auto massResidual = mean_field::utils::blocks::get_residual_block( + mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term + ); + + template + [[nodiscard]] mfem::Vector value_view( + mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::value_block block + ) { + return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block)); + } + + template + void assign_value_block( + mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::value_block block, + const mfem::Vector &source + ) { + MFEM_VERIFY( + source.Size() == layout.size(block), "Source vector has the wrong size for the coupled value block." + ); + + const int offset = layout.offset(block); + + for (int dof = 0; dof < source.Size(); ++dof) { + vector(offset + dof) = source(dof); + } + } + + template + [[nodiscard]] mfem::Vector const_value_view( + const mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::value_block block + ) { + return mfem::Vector(const_cast(vector.GetData()) + layout.offset(block), layout.size(block)); + } + + template + [[nodiscard]] mfem::Vector residual_view( + mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block)); + } + + template + [[nodiscard]] mfem::Vector const_residual_view( + const mfem::Vector &vector, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block + ) { + return mfem::Vector(const_cast(vector.GetData()) + layout.offset(block), layout.size(block)); + } + + [[nodiscard]] mfem::Vector reduce_density( + const mean_field::fem::FEM &f, + const mfem::Vector &fullDensity + ) { + const field::FieldDofMap map = field::make_field_dof_map(*f.densityFes); + return map.gather(fullDensity); + } + + [[nodiscard]] mfem::Vector reduce_enthalpy( + const mean_field::fem::FEM &f, + const mfem::Vector &fullEnthalpy + ) { + const field::FieldDofMap map = field::make_field_dof_map(*f.enthalpyFes); + return map.gather(fullEnthalpy); + } + + [[nodiscard]] mfem::Vector pack_full_gravity_state( + const mean_field::fem::FEM &f, + const mfem::Vector &fullDensity, + const mfem::Vector &displacement, + const mfem::Vector &gravityGradient, + const mfem::Vector &gravityPotential + ) { + const std::array blockSizes{ + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize() + }; + + MFEM_VERIFY(fullDensity.Size() == blockSizes[0], "Full density has the wrong gravity-state size."); + MFEM_VERIFY(displacement.Size() == blockSizes[1], "Displacement has the wrong gravity-state size."); + MFEM_VERIFY(gravityGradient.Size() == blockSizes[2], "Gravity gradient has the wrong gravity-state size."); + MFEM_VERIFY(gravityPotential.Size() == blockSizes[3], "Gravity potential has the wrong gravity-state size."); + + const std::array offsets{ + 0, blockSizes[0], blockSizes[0] + blockSizes[1], blockSizes[0] + blockSizes[1] + blockSizes[2], + blockSizes[0] + blockSizes[1] + blockSizes[2] + blockSizes[3] + }; + + mfem::Vector packed(offsets[4]); + + const std::array blocks{ + &fullDensity, &displacement, &gravityGradient, &gravityPotential + }; + + for (int block = 0; block < 4; ++block) { + mfem::Vector destination(packed.GetData() + offsets[block], blockSizes[block]); + destination = *blocks[block]; + } + + return packed; + } + + [[nodiscard]] mfem::Vector project_density( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.88 + 0.07 * std::sin(0.73 * position(0) + phase) + 0.05 * std::cos(0.59 * position(1) - phase) + + 0.025 * position(2) * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_density_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.16 * std::sin(0.91 * position(0) + phase) - 0.12 * std::cos(0.77 * position(1) - phase) + + 0.06 * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_constant_density( + const mean_field::fem::FEM &f, + const double value + ) { + mfem::ParGridFunction field(f.densityFes.get()); + mfem::ConstantCoefficient coefficient(value); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_displacement( + const mean_field::fem::FEM &f, + const double scale + ) { + return gravity_prepared_test_utils::make_displacement(f, scale); + } + + [[nodiscard]] mfem::Vector project_displacement_direction( + const mean_field::fem::FEM &f, + const double scale + ) { + mfem::ParGridFunction field(f.displacementFes.get()); + mfem::VectorFunctionCoefficient coefficient( + f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = scale * (0.06 * position(0) + 0.014 * position(1) * position(2)); + value(1) = scale * (-0.045 * position(1) + 0.011 * position(0) * position(2)); + value(2) = scale * (0.035 * position(2) - 0.009 * position(0) * position(1)); + } + ); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_gravity_gradient( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityFluxFes.get()); + mfem::VectorFunctionCoefficient coefficient( + f.mesh->Dimension(), [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = 0.27 + 0.07 * position(0) + 0.025 * phase * position(1); + value(1) = -0.19 + 0.055 * position(1) - 0.018 * phase * position(2); + value(2) = 0.21 - 0.045 * position(2) + 0.021 * phase * position(0); + } + ); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_gravity_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityFluxFes.get()); + mfem::VectorFunctionCoefficient coefficient( + f.mesh->Dimension(), [phase](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = 0.13 * std::sin(position(0) + phase) + 0.025 * position(1); + value(1) = -0.10 * std::cos(position(1) - phase) + 0.035 * position(2); + value(2) = 0.08 * std::sin(position(2) + 0.5 * phase) - 0.018 * position(0); + } + ); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_gravity_potential( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityPotentialFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.24 + 0.09 * std::sin(0.67 * position(0) + phase) - 0.06 * std::cos(0.53 * position(1) - phase) + + 0.035 * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_potential_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.gravityPotentialFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.17 * std::sin(0.81 * position(0) + phase) + 0.11 * std::cos(0.69 * position(1) - phase) - + 0.07 * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_enthalpy( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.enthalpyFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.82 + 0.08 * std::sin(0.62 * position(0) + phase) + 0.045 * std::cos(0.57 * position(1) - phase) + + 0.02 * position(2) * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_enthalpy_direction( + const mean_field::fem::FEM &f, + const double phase + ) { + mfem::ParGridFunction field(f.enthalpyFes.get()); + mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) { + return 0.21 * std::sin(0.74 * position(0) + phase) - 0.14 * std::cos(0.64 * position(1) - phase) + + 0.075 * position(2); + }); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mfem::Vector project_constant_scalar( + mfem::ParFiniteElementSpace &finiteElementSpace, + const double value + ) { + mfem::ParGridFunction field(&finiteElementSpace); + mfem::ConstantCoefficient coefficient(value); + field.ProjectCoefficient(coefficient); + mfem::Vector result; + field.GetTrueDofs(result); + return result; + } + + [[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale) { + mfem::Vector angularVelocity(3); + angularVelocity(0) = scale * 0.16; + angularVelocity(1) = scale * -0.08; + angularVelocity(2) = scale * 0.58; + + mfem::Vector center(3); + center(0) = 0.03; + center(1) = -0.025; + center(2) = 0.015; + return mean_field::physics::RigidRotation(angularVelocity, center); + } + + [[nodiscard]] mean_field::physics::RigidRotation make_zero_rotation() { + return make_rotation(0.0); + } + + [[nodiscard]] mean_field::operators::StellarEquilibriumDependencies make_dependencies() { + return { + .discretization = {.identity = 1009, .revision = 3}, + .density = {.identity = 1013, .revision = 5}, + .displacement = {.identity = 1019, .revision = 7}, + .gravityGradient = {.identity = 1021, .revision = 11}, + .gravityPotential = {.identity = 1031, .revision = 13}, + .enthalpy = {.identity = 1033, .revision = 17}, + .bernoulliConstant = {.identity = 1039, .revision = 19}, + .rotation = {.identity = 1049, .revision = 23}, + .targetMass = {.identity = 1051, .revision = 29} + }; + } + + void increment_all_state_revisions(mean_field::operators::StellarEquilibriumDependencies &dependencies) { + ++dependencies.density.revision; + ++dependencies.displacement.revision; + ++dependencies.gravityGradient.revision; + ++dependencies.gravityPotential.revision; + ++dependencies.enthalpy.revision; + ++dependencies.bernoulliConstant.revision; + } + + [[nodiscard]] mfem::Vector make_state( + const mean_field::fem::FEM &f, + const mean_field::operators::StellarEquilibriumLayout &layout + ) { + const FieldMaps maps(f); + + mfem::Vector state(layout.value_offsets().Last()); + state = 0.0; + + { + const mfem::Vector fullDensity = project_density(f, 0.31); + const mfem::Vector reducedDensity = maps.density.gather(fullDensity); + assign_value_block(state, layout, densityValue, reducedDensity); + } + + assign_value_block(state, layout, displacementValue, project_displacement(f, 0.63)); + assign_value_block(state, layout, gravityGradientValue, project_gravity_gradient(f, 0.43)); + assign_value_block(state, layout, gravityPotentialValue, project_gravity_potential(f, 0.47)); + + { + const mfem::Vector fullEnthalpy = project_enthalpy(f, 0.53); + const mfem::Vector reducedEnthalpy = maps.enthalpy.gather(fullEnthalpy); + assign_value_block(state, layout, enthalpyValue, reducedEnthalpy); + } + + value_view(state, layout, bernoulliValue)(0) = 1.07; + + return state; + } + + [[nodiscard]] mfem::Vector make_direction( + const mean_field::fem::FEM &f, + const mean_field::operators::StellarEquilibriumLayout &layout + ) { + const FieldMaps maps(f); + + mfem::Vector direction(layout.value_offsets().Last()); + direction = 0.0; + + { + const mfem::Vector fullDensityDirection = project_density_direction(f, 0.61); + const mfem::Vector reducedDensityDirection = maps.density.gather(fullDensityDirection); + assign_value_block(direction, layout, densityValue, reducedDensityDirection); + } + + assign_value_block(direction, layout, displacementValue, project_displacement_direction(f, 0.79)); + assign_value_block(direction, layout, gravityGradientValue, project_gravity_direction(f, 0.83)); + assign_value_block(direction, layout, gravityPotentialValue, project_potential_direction(f, 0.89)); + + { + const mfem::Vector fullEnthalpyDirection = project_enthalpy_direction(f, 0.97); + const mfem::Vector reducedEnthalpyDirection = maps.enthalpy.gather(fullEnthalpyDirection); + assign_value_block(direction, layout, enthalpyValue, reducedEnthalpyDirection); + } + + value_view(direction, layout, bernoulliValue)(0) = -0.37; + + return direction; + } + + [[nodiscard]] double global_norm( + const mfem::Vector &vector, + const MPI_Comm communicator + ) { + return gravity_prepared_test_utils::global_norm(vector, communicator); + } + + [[nodiscard]] double relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const MPI_Comm communicator + ) { + mfem::Vector difference(left); + difference -= right; + const double scale = std::max( + {global_norm(left, communicator), global_norm(right, communicator), + 100.0 * std::numeric_limits::epsilon()} + ); + return global_norm(difference, communicator) / scale; + } + + [[nodiscard]] mfem::Vector explicit_residual( + const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator, + const mean_field::fem::FEM &f, + const mfem::Vector &state + ) { + const mean_field::operators::StellarEquilibriumLayout &layout = stellarOperator.GetLayout(); + const FieldMaps maps(f); + + const mfem::Vector reducedDensity = const_value_view(state, layout, densityValue); + const mfem::Vector displacement = const_value_view(state, layout, displacementValue); + const mfem::Vector gravityGradient = const_value_view(state, layout, gravityGradientValue); + const mfem::Vector gravityPotential = const_value_view(state, layout, gravityPotentialValue); + + const mfem::Vector fullDensity = maps.density.scatter(reducedDensity); + const mfem::Vector gravityState = + pack_full_gravity_state(f, fullDensity, displacement, gravityGradient, gravityPotential); + + mfem::Vector gravity; + mfem::Vector closure; + mfem::Vector displacementResidualValue; + mfem::Vector fullHydrostatic; + mfem::Vector mass; + + stellarOperator.GetGravityOperator().Mult(gravityState, gravity); + stellarOperator.GetBarotropicClosureOperator().BuildResidual(closure); + stellarOperator.GetDisplacementOperator().BuildResidual(displacementResidualValue); + stellarOperator.GetHydrostaticOperator().BuildResidual(fullHydrostatic); + stellarOperator.GetMassNormalizationOperator().BuildResidual(mass); + + mfem::Vector result(layout.residual_offsets().Last()); + result = 0.0; + + MFEM_VERIFY( + gravity.Size() == layout.size(gravityGradientResidual) + layout.size(gravityPotentialResidual), + "Explicit gravity residual has the wrong size." + ); + + const mfem::Vector gravityGradientResidualValue(gravity.GetData(), layout.size(gravityGradientResidual)); + const mfem::Vector gravityPotentialResidualValue( + gravity.GetData() + layout.size(gravityGradientResidual), layout.size(gravityPotentialResidual) + ); + + residual_view(result, layout, gravityGradientResidual) = gravityGradientResidualValue; + residual_view(result, layout, gravityPotentialResidual) = gravityPotentialResidualValue; + + residual_view(result, layout, densityResidual) = closure; + + residual_view(result, layout, displacementResidual) = displacementResidualValue; + + { + mfem::Vector reducedHydrostatic = residual_view(result, layout, enthalpyResidual); + maps.enthalpy.gather(fullHydrostatic, reducedHydrostatic); + } + + residual_view(result, layout, massResidual) = mass; + + return result; + } + + [[nodiscard]] mfem::Vector explicit_jacobian_action( + const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator, + const mean_field::fem::FEM &f, + const mfem::Vector &direction + ) { + const mean_field::operators::StellarEquilibriumLayout &layout = stellarOperator.GetLayout(); + const FieldMaps maps(f); + + const mfem::Vector reducedDensityDirection = const_value_view(direction, layout, densityValue); + const mfem::Vector displacementDirection = const_value_view(direction, layout, displacementValue); + const mfem::Vector gravityGradientDirection = const_value_view(direction, layout, gravityGradientValue); + const mfem::Vector gravityPotentialDirection = const_value_view(direction, layout, gravityPotentialValue); + const mfem::Vector reducedEnthalpyDirection = const_value_view(direction, layout, enthalpyValue); + const mfem::Vector bernoulliDirection = const_value_view(direction, layout, bernoulliValue); + + const mfem::Vector fullDensityDirection = maps.density.scatter(reducedDensityDirection); + const mfem::Vector fullEnthalpyDirection = maps.enthalpy.scatter(reducedEnthalpyDirection); + + const mfem::Vector gravityDirection = pack_full_gravity_state( + f, fullDensityDirection, displacementDirection, gravityGradientDirection, gravityPotentialDirection + ); + + mfem::Vector gravityAction; + mfem::Vector closureAction; + mfem::Vector displacementAction; + mfem::Vector fullHydrostaticAction; + mfem::Vector massAction; + + stellarOperator.GetGravityJacobianOperator().Mult(gravityDirection, gravityAction); + + stellarOperator.GetBarotropicClosureOperator().Mult( + reducedDensityDirection, reducedEnthalpyDirection, displacementDirection, closureAction + ); + + stellarOperator.GetDisplacementOperator().ApplyCompleteJacobianAction( + fullDensityDirection, displacementDirection, gravityGradientDirection, fullEnthalpyDirection, + displacementAction + ); + + stellarOperator.GetHydrostaticOperator().ApplyCompleteJacobianAction( + fullEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection, + fullHydrostaticAction + ); + + stellarOperator.GetMassNormalizationOperator().ApplyCompleteJacobianAction( + fullDensityDirection, displacementDirection, massAction + ); + + mfem::Vector result(layout.residual_offsets().Last()); + result = 0.0; + + MFEM_VERIFY( + gravityAction.Size() == layout.size(gravityGradientResidual) + layout.size(gravityPotentialResidual), + "Explicit gravity Jacobian action has the wrong size." + ); + + const mfem::Vector gravityGradientAction(gravityAction.GetData(), layout.size(gravityGradientResidual)); + const mfem::Vector gravityPotentialAction( + gravityAction.GetData() + layout.size(gravityGradientResidual), layout.size(gravityPotentialResidual) + ); + + residual_view(result, layout, gravityGradientResidual) = gravityGradientAction; + residual_view(result, layout, gravityPotentialResidual) = gravityPotentialAction; + + residual_view(result, layout, densityResidual) = closureAction; + + residual_view(result, layout, displacementResidual) = displacementAction; + + { + mfem::Vector reducedHydrostaticAction = residual_view(result, layout, enthalpyResidual); + maps.enthalpy.gather(fullHydrostaticAction, reducedHydrostaticAction); + } + + residual_view(result, layout, massResidual) = massAction; + + return result; + } + + [[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; + } + + template + [[nodiscard]] double block_relative_difference( + const mfem::Vector &left, + const mfem::Vector &right, + const mean_field::operators::StellarEquilibriumLayout &layout, + const mean_field::utils::blocks::residual_block block, + const MPI_Comm communicator + ) { + return relative_difference( + const_residual_view(left, layout, block), const_residual_view(right, layout, block), communicator + ); + } +} // namespace stellar_equilibrium_test_utils + +TEST_CASE( + "Prepared Stellar Equilibrium Uses Supported Field DOFs For Solver Blocks", + tags::barotrope &tags::prepared &tags::field &tags::unit +) { + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.0 + ); + + const auto &layout = stellarOperator.GetLayout(); + const stellar_equilibrium_test_utils::FieldMaps maps(f); + + CHECK(layout.size(stellar_equilibrium_test_utils::densityValue) == maps.density.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::displacementValue) == maps.displacement.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::gravityGradientValue) == maps.gravityFlux.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::gravityPotentialValue) == maps.gravityPotential.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::enthalpyValue) == maps.enthalpy.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::bernoulliValue) == 1); + + CHECK(layout.size(stellar_equilibrium_test_utils::gravityGradientResidual) == maps.gravityFlux.reduced_size()); + CHECK( + layout.size(stellar_equilibrium_test_utils::gravityPotentialResidual) == maps.gravityPotential.reduced_size() + ); + CHECK(layout.size(stellar_equilibrium_test_utils::densityResidual) == maps.density.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::displacementResidual) == maps.displacement.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::enthalpyResidual) == maps.enthalpy.reduced_size()); + CHECK(layout.size(stellar_equilibrium_test_utils::massResidual) == 1); + + CHECK(maps.displacement.is_identity()); + CHECK(maps.gravityFlux.is_identity()); + CHECK(maps.gravityPotential.is_identity()); + + CHECK(stellarOperator.Width() == layout.value_offsets().Last()); + CHECK(stellarOperator.Height() == layout.residual_offsets().Last()); + + const MPI_Comm communicator = f.mesh->GetComm(); + + const long long globalDensityFull = + stellar_equilibrium_test_utils::global_sum(maps.density.full_size(), communicator); + const long long globalDensityReduced = + stellar_equilibrium_test_utils::global_sum(maps.density.reduced_size(), communicator); + + const long long globalEnthalpyFull = + stellar_equilibrium_test_utils::global_sum(maps.enthalpy.full_size(), communicator); + const long long globalEnthalpyReduced = + stellar_equilibrium_test_utils::global_sum(maps.enthalpy.reduced_size(), communicator); + + INFO("Global density full true DOFs = " << globalDensityFull); + INFO("Global density solver DOFs = " << globalDensityReduced); + INFO("Global enthalpy full true DOFs = " << globalEnthalpyFull); + INFO("Global enthalpy solver DOFs = " << globalEnthalpyReduced); + + REQUIRE(globalDensityFull > 0); + REQUIRE(globalEnthalpyFull > 0); + + CHECK(globalDensityReduced > 0); + CHECK(globalDensityReduced < globalDensityFull); + + CHECK(globalEnthalpyReduced > 0); + CHECK(globalEnthalpyReduced < globalEnthalpyFull); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Jacobian Is The Exact Restricted Full Child Jacobian", + tags::barotrope &tags::prepared &tags::field &tags::jacobian &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()); + + const mean_field::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.19 + ); + + const auto &layout = stellarOperator.GetLayout(); + const mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout); + const mfem::Vector direction = stellar_equilibrium_test_utils::make_direction(f, layout); + const auto dependencies = stellar_equilibrium_test_utils::make_dependencies(); + const auto rotation = stellar_equilibrium_test_utils::make_rotation(0.82); + + stellarOperator.Prepare(state, dependencies, rotation); + + mfem::Vector rootAction; + stellarOperator.Mult(direction, rootAction); + + const mfem::Vector explicitAction = + stellar_equilibrium_test_utils::explicit_jacobian_action(stellarOperator, f, direction); + + const double difference = + stellar_equilibrium_test_utils::relative_difference(rootAction, explicitAction, f.mesh->GetComm()); + + INFO("Reduced root versus explicit R J P relative difference = " << difference); + + CHECK(difference < 2.0e-15); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Owns And Composes Every Fixed Residual Row", + tags::barotrope &tags::prepared &tags::integration &tags::residuals +) { + using Operator = mean_field::operators::PreparedStellarEquilibriumOperator; + + 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); + + 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::eos::Polytrope barotrope(3.0, 0.25); + Operator stellarOperator(f, *f.domainMapperStateless, barotrope, 1.13); + + const mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, stellarOperator.GetLayout()); + const auto dependencies = stellar_equilibrium_test_utils::make_dependencies(); + const auto rotation = stellar_equilibrium_test_utils::make_rotation(0.81); + + const auto report = stellarOperator.Prepare(state, dependencies, rotation); + + CHECK(report.gravity.DidAnyWork()); + CHECK(report.barotropicClosure.DidAnyWork()); + CHECK(report.hydrostatic.DidAnyWork()); + CHECK(report.displacement.DidAnyWork()); + CHECK(report.massNormalization.DidAnyWork()); + CHECK(report.assembledResidual); + CHECK(stellarOperator.IsPrepared()); + + CHECK(&stellarOperator.GetGravityOperator().GetLinearizationContext() == &stellarOperator.GetGravityContext()); + CHECK(&stellarOperator.GetDisplacementOperator().GetGravityContext() == &stellarOperator.GetGravityContext()); + CHECK(&stellarOperator.GetMassNormalizationOperator().GetGravityContext() == &stellarOperator.GetGravityContext()); + CHECK( + &stellarOperator.GetBarotropicClosureOperator().GetContext() == &stellarOperator.GetBarotropicClosureContext() + ); + + mfem::Vector coupledResidual; + stellarOperator.BuildResidual(coupledResidual); + const mfem::Vector expected = stellar_equilibrium_test_utils::explicit_residual(stellarOperator, f, state); + + CHECK(stellar_equilibrium_test_utils::relative_difference(coupledResidual, expected, f.mesh->GetComm()) < 2.0e-15); + + CHECK(stellarOperator.Width() == stellarOperator.GetLayout().value_offsets().Last()); + CHECK(stellarOperator.Height() == stellarOperator.GetLayout().residual_offsets().Last()); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Has Exact Analytic Closure Hydrostatic And Mass Rows", + tags::barotrope &tags::prepared &tags::analytic_comparison &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()); + + const mean_field::eos::Polytrope barotrope(1.0, 0.25); + constexpr double enthalpy = 0.60; + const double density = barotrope.density_from_enthalpy(enthalpy); + constexpr double gravityPotential = 0.20; + constexpr double bernoulliConstant = enthalpy + gravityPotential; + + const mean_field::mapping::COORDINATE_SPACE volumeCoordinates = + f.has_mapping() ? mean_field::mapping::COORDINATE_SPACE::PHYSICAL + : mean_field::mapping::COORDINATE_SPACE::REFERENCE; + + const double targetMass = + density * mean_field::analysis::get_mesh_volume(f, volumeCoordinates, mean_field::utils::DOMAINS::STELLAR); + + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, targetMass + ); + + const auto &layout = stellarOperator.GetLayout(); + mfem::Vector state(layout.value_offsets().Last()); + state = 0.0; + + stellar_equilibrium_test_utils::assign_value_block( + state, layout, stellar_equilibrium_test_utils::densityValue, + stellar_equilibrium_test_utils::reduce_density( + f, stellar_equilibrium_test_utils::project_constant_density(f, density) + ) + ); + + stellar_equilibrium_test_utils::assign_value_block( + state, layout, stellar_equilibrium_test_utils::gravityPotentialValue, + stellar_equilibrium_test_utils::project_constant_scalar(*f.gravityPotentialFes, gravityPotential) + ); + + stellar_equilibrium_test_utils::assign_value_block( + state, layout, stellar_equilibrium_test_utils::enthalpyValue, + stellar_equilibrium_test_utils::reduce_enthalpy( + f, stellar_equilibrium_test_utils::project_constant_scalar(*f.enthalpyFes, enthalpy) + ) + ); + + stellar_equilibrium_test_utils::value_view(state, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) = + bernoulliConstant; + + stellarOperator.Prepare( + state, stellar_equilibrium_test_utils::make_dependencies(), stellar_equilibrium_test_utils::make_zero_rotation() + ); + + mfem::Vector residual; + stellarOperator.BuildResidual(residual); + + const double closureNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + residual, layout, stellar_equilibrium_test_utils::densityResidual + ), + f.mesh->GetComm() + ); + const double hydrostaticNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + residual, layout, stellar_equilibrium_test_utils::enthalpyResidual + ), + f.mesh->GetComm() + ); + const double massError = std::abs( + stellar_equilibrium_test_utils:: + const_residual_view(residual, layout, stellar_equilibrium_test_utils::massResidual)(0) + ); + + INFO("Exact n=1 closure norm = " << closureNorm); + INFO("Exact constant hydrostatic norm = " << hydrostaticNorm); + INFO("Independent constant-density mass error = " << massError); + + CHECK(closureNorm < 2.0e-12); + CHECK(hydrostaticNorm < 2.0e-12); + CHECK(massError < 2.0e-11 * targetMass); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Zero Gravity State Has Analytically Zero Gravity Rows", + tags::gravity &tags::prepared &tags::analytic_comparison &tags::residuals +) { + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.0 + ); + + const auto &layout = stellarOperator.GetLayout(); + mfem::Vector state(layout.value_offsets().Last()); + state = 0.0; + + stellar_equilibrium_test_utils::assign_value_block( + state, layout, stellar_equilibrium_test_utils::displacementValue, + stellar_equilibrium_test_utils::project_displacement(f, 0.73) + ); + stellarOperator.Prepare( + state, stellar_equilibrium_test_utils::make_dependencies(), stellar_equilibrium_test_utils::make_zero_rotation() + ); + + mfem::Vector residual; + stellarOperator.BuildResidual(residual); + + const double gradientNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + residual, layout, stellar_equilibrium_test_utils::gravityGradientResidual + ), + f.mesh->GetComm() + ); + const double poissonNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + residual, layout, stellar_equilibrium_test_utils::gravityPotentialResidual + ), + f.mesh->GetComm() + ); + + CHECK(gradientNorm == 0.0); + CHECK(poissonNorm == 0.0); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Jacobian Has The Declared Six By Six Shape", + tags::barotrope &tags::prepared &tags::jacobian &tags::mfem_operators &tags::unit +) { + using JacobianForm = mean_field::utils::blocks::barotropic_equilibrium_jacobian_form; + + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + mean_field::utils::blocks::barotropic_constant::mass_normalization::residual, + mean_field::utils::blocks::density::mass::value, JacobianForm> + ); + STATIC_REQUIRE( + mean_field::utils::blocks::has_jacobian_coupling_v< + mean_field::utils::blocks::barotropic_constant::mass_normalization::residual, + mean_field::utils::blocks::displacement::geometry::value, JacobianForm> + ); + STATIC_REQUIRE_FALSE( + mean_field::utils::blocks::has_jacobian_coupling_v< + mean_field::utils::blocks::barotropic_constant::mass_normalization::residual, + mean_field::utils::blocks::enthalpy::specific::value, JacobianForm> + ); + + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.17 + ); + const auto &layout = stellarOperator.GetLayout(); + const mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout); + stellarOperator.Prepare( + state, stellar_equilibrium_test_utils::make_dependencies(), stellar_equilibrium_test_utils::make_rotation(0.77) + ); + + const mfem::Vector fullDirection = stellar_equilibrium_test_utils::make_direction(f, layout); + + struct ShapeCase final { + int activeColumn; + std::array allowedRows; + }; + + const std::array cases{ + ShapeCase{0, {false, true, true, true, false, true}}, ShapeCase{1, {true, true, true, true, true, true}}, + ShapeCase{2, {true, true, false, true, false, false}}, ShapeCase{3, {true, false, false, false, true, false}}, + ShapeCase{4, {false, false, true, true, true, false}}, ShapeCase{5, {false, false, false, false, true, false}} + }; + + const std::array valueOffsets{ + layout.offset(stellar_equilibrium_test_utils::densityValue), + layout.offset(stellar_equilibrium_test_utils::displacementValue), + layout.offset(stellar_equilibrium_test_utils::gravityGradientValue), + layout.offset(stellar_equilibrium_test_utils::gravityPotentialValue), + layout.offset(stellar_equilibrium_test_utils::enthalpyValue), + layout.offset(stellar_equilibrium_test_utils::bernoulliValue), + layout.value_offsets().Last() + }; + + for (const ShapeCase &shapeCase : cases) { + CAPTURE(shapeCase.activeColumn); + + mfem::Vector columnDirection(fullDirection.Size()); + columnDirection = 0.0; + + for (int entry = valueOffsets[shapeCase.activeColumn]; entry < valueOffsets[shapeCase.activeColumn + 1]; + ++entry) { + columnDirection(entry) = fullDirection(entry); + } + + mfem::Vector action; + stellarOperator.Mult(columnDirection, action); + + const std::array rowActions{ + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::gravityGradientResidual + ), + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::gravityPotentialResidual + ), + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::densityResidual + ), + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::displacementResidual + ), + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::enthalpyResidual + ), + stellar_equilibrium_test_utils::const_residual_view( + action, layout, stellar_equilibrium_test_utils::massResidual + ) + }; + + double allowedNormSquared = 0.0; + + for (int row = 0; row < 6; ++row) { + CAPTURE(row); + + const double rowNorm = stellar_equilibrium_test_utils::global_norm(rowActions[row], f.mesh->GetComm()); + + if (shapeCase.allowedRows[row]) { + allowedNormSquared += rowNorm * rowNorm; + } else { + CHECK(rowNorm == 0.0); + } + } + + CHECK(allowedNormSquared > 0.0); + } +} + +TEST_CASE( + "Prepared Stellar Equilibrium Complete Jacobian Matches Every Coupled Centered Difference Block", + tags::barotrope &tags::prepared &tags::jacobian &tags::accuracy &tags::geometry +) { + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.21 + ); + const auto &layout = stellarOperator.GetLayout(); + const mfem::Vector baseState = stellar_equilibrium_test_utils::make_state(f, layout); + const mfem::Vector direction = stellar_equilibrium_test_utils::make_direction(f, layout); + auto dependencies = stellar_equilibrium_test_utils::make_dependencies(); + const auto rotation = stellar_equilibrium_test_utils::make_rotation(0.85); + + stellarOperator.Prepare(baseState, dependencies, rotation); + + mfem::Vector analyticAction; + stellarOperator.Mult(direction, analyticAction); + + constexpr double step = 1.0e-5; + mfem::Vector plusState(baseState); + plusState.Add(step, direction); + stellar_equilibrium_test_utils::increment_all_state_revisions(dependencies); + stellarOperator.Prepare(plusState, dependencies, rotation); + mfem::Vector plusResidual; + stellarOperator.BuildResidual(plusResidual); + + mfem::Vector minusState(baseState); + minusState.Add(-step, direction); + stellar_equilibrium_test_utils::increment_all_state_revisions(dependencies); + stellarOperator.Prepare(minusState, dependencies, rotation); + mfem::Vector minusResidual; + stellarOperator.BuildResidual(minusResidual); + + plusResidual -= minusResidual; + plusResidual /= 2.0 * step; + + const std::array errors{ + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::gravityGradientResidual, + f.mesh->GetComm() + ), + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::gravityPotentialResidual, + f.mesh->GetComm() + ), + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::densityResidual, f.mesh->GetComm() + ), + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::displacementResidual, + f.mesh->GetComm() + ), + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::enthalpyResidual, f.mesh->GetComm() + ), + stellar_equilibrium_test_utils::block_relative_difference( + analyticAction, plusResidual, layout, stellar_equilibrium_test_utils::massResidual, f.mesh->GetComm() + ) + }; + + INFO("R_g centered-difference error = " << errors[0]); + INFO("R_Phi centered-difference error = " << errors[1]); + INFO("R_rho centered-difference error = " << errors[2]); + INFO("R_d centered-difference error = " << errors[3]); + INFO("R_h centered-difference error = " << errors[4]); + INFO("R_M centered-difference error = " << errors[5]); + + CHECK(errors[0] < 2.0e-6); + CHECK(errors[1] < 2.0e-6); + CHECK(errors[2] < 2.0e-6); + CHECK(errors[3] < 2.0e-6); + CHECK(errors[4] < 2.0e-6); + CHECK(errors[5] < 2.0e-6); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Bernoulli Newton Step Decreases The Residual Exactly", + tags::barotrope &tags::prepared &tags::jacobian &tags::convergence +) { + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.09 + ); + const auto &layout = stellarOperator.GetLayout(); + mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout); + auto dependencies = stellar_equilibrium_test_utils::make_dependencies(); + const auto rotation = stellar_equilibrium_test_utils::make_rotation(0.69); + + stellarOperator.Prepare(state, dependencies, rotation); + mfem::Vector residualBefore; + stellarOperator.BuildResidual(residualBefore); + + mfem::Vector unitBernoulliDirection(state.Size()); + unitBernoulliDirection = 0.0; + stellar_equilibrium_test_utils:: + value_view(unitBernoulliDirection, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) = 1.0; + + mfem::Vector bernoulliAction; + stellarOperator.Mult(unitBernoulliDirection, bernoulliAction); + + const mfem::Vector residualHydrostatic = stellar_equilibrium_test_utils::const_residual_view( + residualBefore, layout, stellar_equilibrium_test_utils::enthalpyResidual + ); + const mfem::Vector actionHydrostatic = stellar_equilibrium_test_utils::const_residual_view( + bernoulliAction, layout, stellar_equilibrium_test_utils::enthalpyResidual + ); + + const double numerator = + gravity_prepared_test_utils::global_dot(residualHydrostatic, actionHydrostatic, f.mesh->GetComm()); + const double denominator = + gravity_prepared_test_utils::global_dot(actionHydrostatic, actionHydrostatic, f.mesh->GetComm()); + REQUIRE(denominator > 0.0); + const double bernoulliStep = -numerator / denominator; + + mfem::Vector predictedResidual(residualBefore); + predictedResidual.Add(bernoulliStep, bernoulliAction); + + stellar_equilibrium_test_utils::value_view(state, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) += + bernoulliStep; + ++dependencies.bernoulliConstant.revision; + + stellarOperator.Prepare(state, dependencies, rotation); + mfem::Vector residualAfter; + stellarOperator.BuildResidual(residualAfter); + + const double modelError = + stellar_equilibrium_test_utils::relative_difference(residualAfter, predictedResidual, f.mesh->GetComm()); + + const double hydrostaticNormBefore = + stellar_equilibrium_test_utils::global_norm(residualHydrostatic, f.mesh->GetComm()); + const double hydrostaticNormAfter = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + residualAfter, layout, stellar_equilibrium_test_utils::enthalpyResidual + ), + f.mesh->GetComm() + ); + const double coupledNormBefore = stellar_equilibrium_test_utils::global_norm(residualBefore, f.mesh->GetComm()); + const double coupledNormAfter = stellar_equilibrium_test_utils::global_norm(residualAfter, f.mesh->GetComm()); + + INFO("Bernoulli least-squares step = " << bernoulliStep); + INFO("Exact affine residual-model error = " << modelError); + INFO("Hydrostatic norm before = " << hydrostaticNormBefore); + INFO("Hydrostatic norm after = " << hydrostaticNormAfter); + INFO("Coupled norm before = " << coupledNormBefore); + INFO("Coupled norm after = " << coupledNormAfter); + + CHECK(modelError < 2.0e-13); + CHECK(hydrostaticNormAfter < hydrostaticNormBefore); + CHECK(coupledNormAfter <= coupledNormBefore); + + mfem::Vector unchangedDifference(residualAfter); + unchangedDifference -= residualBefore; + stellar_equilibrium_test_utils::residual_view( + unchangedDifference, layout, stellar_equilibrium_test_utils::enthalpyResidual + ) = 0.0; + CHECK(stellar_equilibrium_test_utils::global_norm(unchangedDifference, f.mesh->GetComm()) == 0.0); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Selectively Invalidates Rows And Never Reprepares In Krylov Mult", + tags::barotrope &tags::prepared &tags::contexts &tags::mfem_operators +) { + 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::eos::Polytrope barotrope(3.0, 0.25); + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, 1.15 + ); + const auto &layout = stellarOperator.GetLayout(); + mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout); + auto dependencies = stellar_equilibrium_test_utils::make_dependencies(); + const auto rotation = stellar_equilibrium_test_utils::make_rotation(0.73); + + stellarOperator.Prepare(state, dependencies, rotation); + + const std::uint64_t closurePreparations = stellarOperator.GetBarotropicClosureOperator().GetPreparationCount(); + const std::uint64_t hydrostaticPreparations = + stellarOperator.GetHydrostaticOperator().GetResidualPreparationCount(); + const std::uint64_t displacementPreparations = + stellarOperator.GetDisplacementOperator().GetResidualPreparationCount(); + const std::uint64_t massPreparations = stellarOperator.GetMassNormalizationOperator().GetPreparationCount(); + const std::uint64_t rootAssemblies = stellarOperator.GetStatistics().residualAssemblies; + + const auto repeated = stellarOperator.Prepare(state, dependencies, rotation); + CHECK_FALSE(repeated.DidAnyWork()); + CHECK(stellarOperator.GetStatistics().residualAssemblies == rootAssemblies); + + const mfem::Vector direction = stellar_equilibrium_test_utils::make_direction(f, layout); + mfem::Vector action; + stellarOperator.Mult(direction, action); + stellarOperator.Mult(direction, action); + stellarOperator.Mult(direction, action); + + CHECK(stellarOperator.GetBarotropicClosureOperator().GetPreparationCount() == closurePreparations); + CHECK(stellarOperator.GetHydrostaticOperator().GetResidualPreparationCount() == hydrostaticPreparations); + CHECK(stellarOperator.GetDisplacementOperator().GetResidualPreparationCount() == displacementPreparations); + CHECK(stellarOperator.GetMassNormalizationOperator().GetPreparationCount() == massPreparations); + CHECK(stellarOperator.GetStatistics().residualAssemblies == rootAssemblies); + CHECK(stellarOperator.GetStatistics().jacobianApplications == 3); + + stellar_equilibrium_test_utils::value_view(state, layout, stellar_equilibrium_test_utils::gravityPotentialValue) + .Add(0.03, stellar_equilibrium_test_utils::project_potential_direction(f, 0.41)); + ++dependencies.gravityPotential.revision; + + const auto potentialReport = stellarOperator.Prepare(state, dependencies, rotation); + + CHECK_FALSE(potentialReport.barotropicClosure.DidAnyWork()); + CHECK(potentialReport.hydrostatic.DidAnyWork()); + CHECK_FALSE(potentialReport.displacement.DidAnyWork()); + CHECK_FALSE(potentialReport.massNormalization.DidAnyWork()); + CHECK(potentialReport.assembledResidual); + + stellar_equilibrium_test_utils::value_view(state, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) += + 0.09; + ++dependencies.bernoulliConstant.revision; + + const auto bernoulliReport = stellarOperator.Prepare(state, dependencies, rotation); + + CHECK_FALSE(bernoulliReport.barotropicClosure.DidAnyWork()); + CHECK(bernoulliReport.hydrostatic.DidAnyWork()); + CHECK_FALSE(bernoulliReport.displacement.DidAnyWork()); + CHECK_FALSE(bernoulliReport.massNormalization.DidAnyWork()); + CHECK(bernoulliReport.assembledResidual); + + ++dependencies.targetMass.revision; + const auto targetReport = stellarOperator.Prepare(state, dependencies, rotation); + + CHECK_FALSE(targetReport.barotropicClosure.DidAnyWork()); + CHECK_FALSE(targetReport.hydrostatic.DidAnyWork()); + CHECK_FALSE(targetReport.displacement.DidAnyWork()); + CHECK(targetReport.massNormalization.DidAnyWork()); + CHECK(targetReport.assembledResidual); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Matches The Analytic N1 Lane Emden State Up To The Mixed Projection Floor", + tags::barotrope &tags::prepared &tags::analytic_comparison &tags::accuracy &tags::gravity &tags::hydro + &tags::residuals +) { + class LaneEmdenGravityGradientCoefficient final : public mfem::VectorCoefficient { + public: + LaneEmdenGravityGradientCoefficient( + const int dimension, + const int vacuumAttribute, + const double stellarRadius, + const double centralDensity, + const double targetMass, + const double polytropicConstant + ) + : mfem::VectorCoefficient(dimension), + m_vacuumAttribute(vacuumAttribute), + m_stellarRadius(stellarRadius), + m_centralDensity(centralDensity), + m_targetMass(targetMass), + m_polytropicConstant(polytropicConstant) { + } + + void Eval( + mfem::Vector &value, + mfem::ElementTransformation &transformation, + const mfem::IntegrationPoint &integrationPoint + ) override { + mfem::Vector computationalPosition; + transformation.Transform(integrationPoint, computationalPosition); + + value.SetSize(vdim); + value = 0.0; + + const double radius = computationalPosition.Norml2(); + + if (!std::isfinite(radius) || radius <= 100.0 * std::numeric_limits::epsilon()) { + return; + } + + double radialGradient = 0.0; + + if (transformation.Attribute == m_vacuumAttribute) { + /* + * In the compactified exterior, the three-dimensional H(div) + * Piola pullback of the inverse-square monopole field reduces + * to this finite computational-space expression. + */ + radialGradient = mean_field::utils::G * m_targetMass / (radius * radius); + } else { + const double pi = std::acos(-1.0); + const double xi = pi * radius / m_stellarRadius; + + if (std::abs(xi) < 1.0e-5) { + /* + * sin(xi) - xi cos(xi) = xi^3 / 3 + O(xi^5). + */ + radialGradient = (4.0 / 3.0) * pi * mean_field::utils::G * m_centralDensity * radius; + } else { + radialGradient = 2.0 * m_polytropicConstant * m_centralDensity * pi / m_stellarRadius * + (std::sin(xi) - xi * std::cos(xi)) / (xi * xi); + } + } + + value = computationalPosition; + value *= radialGradient / radius; + } + + private: + int m_vacuumAttribute; + double m_stellarRadius; + double m_centralDensity; + double m_targetMass; + double m_polytropicConstant; + }; + + 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.mapping != nullptr); + REQUIRE(f.domainMapperStateless != nullptr); + + f.mapping->ResetDisplacement(); + + const double pi = std::acos(-1.0); + const double stellarRadius = mean_field::utils::RADIUS; + const double targetMass = mean_field::utils::MASS; + + /* + * For an n = 1 Lane-Emden polytrope, + * + * R = sqrt(pi K / (2 G)), + * + * so choosing K this way places the analytic surface exactly at the + * stellar boundary of the mesh. + */ + const double polytropicConstant = 2.0 * mean_field::utils::G * stellarRadius * stellarRadius / pi; + + /* + * The analytic n = 1 mass is + * + * M = 4 rho_c R^3 / pi. + */ + const double centralDensity = pi * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius); + + const double bernoulliConstant = -mean_field::utils::G * targetMass / stellarRadius; + + const mean_field::eos::Polytrope barotrope(1.0, polytropicConstant); + + const auto densityFunction = [centralDensity, stellarRadius, pi](const mfem::Vector &position) { + const double radius = position.Norml2(); + + if (radius >= stellarRadius) { + return 0.0; + } + + const double xi = pi * radius / stellarRadius; + + if (std::abs(xi) < 100.0 * std::numeric_limits::epsilon()) { + return centralDensity; + } + + return centralDensity * std::sin(xi) / xi; + }; + + const auto enthalpyFunction = [centralDensity, stellarRadius, polytropicConstant, + pi](const mfem::Vector &position) { + const double radius = position.Norml2(); + + if (radius >= stellarRadius) { + return 0.0; + } + + const double xi = pi * radius / stellarRadius; + + const double density = std::abs(xi) < 100.0 * std::numeric_limits::epsilon() + ? centralDensity + : centralDensity * std::sin(xi) / xi; + + return 2.0 * polytropicConstant * density; + }; + + const auto potentialFunction = [centralDensity, stellarRadius, targetMass, polytropicConstant, bernoulliConstant, + pi](const mfem::Vector &physicalPosition) { + const double radius = physicalPosition.Norml2(); + + /* + * Phi tends to zero at compactified infinity. + */ + if (!std::isfinite(radius)) { + return 0.0; + } + + if (radius >= stellarRadius) { + return radius > 0.0 ? -mean_field::utils::G * targetMass / radius : 0.0; + } + + const double xi = pi * radius / stellarRadius; + + const double density = std::abs(xi) < 100.0 * std::numeric_limits::epsilon() + ? centralDensity + : centralDensity * std::sin(xi) / xi; + + const double enthalpy = 2.0 * polytropicConstant * density; + + /* + * Hydrostatic equilibrium is h + Phi = C. + */ + return bernoulliConstant - enthalpy; + }; + + mfem::FunctionCoefficient densityCoefficient(densityFunction); + mfem::FunctionCoefficient enthalpyCoefficient(enthalpyFunction); + + mean_field::mapping::PhysicalPositionFunctionCoefficient potentialCoefficient(*f.mapping, potentialFunction); + + LaneEmdenGravityGradientCoefficient gravityGradientCoefficient( + f.mesh->Dimension(), f.domainMapperStateless->GetVacuumElementAttribute(), stellarRadius, centralDensity, + targetMass, polytropicConstant + ); + + mfem::ParGridFunction densityField(f.densityFes.get()); + mfem::ParGridFunction enthalpyField(f.enthalpyFes.get()); + mfem::ParGridFunction gravityPotentialField(f.gravityPotentialFes.get()); + mfem::ParGridFunction gravityGradientField(f.gravityFluxFes.get()); + + densityField = 0.0; + enthalpyField = 0.0; + gravityPotentialField = 0.0; + gravityGradientField = 0.0; + + densityField.ProjectCoefficient(densityCoefficient); + enthalpyField.ProjectCoefficient(enthalpyCoefficient); + gravityPotentialField.ProjectCoefficient(potentialCoefficient); + gravityGradientField.ProjectCoefficient(gravityGradientCoefficient); + + mfem::Vector densityTrue; + mfem::Vector enthalpyTrue; + mfem::Vector gravityPotentialTrue; + mfem::Vector gravityGradientTrue; + + densityField.GetTrueDofs(densityTrue); + enthalpyField.GetTrueDofs(enthalpyTrue); + gravityPotentialField.GetTrueDofs(gravityPotentialTrue); + gravityGradientField.GetTrueDofs(gravityGradientTrue); + + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, targetMass + ); + + const mean_field::operators::StellarEquilibriumLayout &layout = stellarOperator.GetLayout(); + + mfem::Vector analyticState(layout.value_offsets().Last()); + analyticState = 0.0; + + stellar_equilibrium_test_utils::assign_value_block( + analyticState, layout, stellar_equilibrium_test_utils::densityValue, + stellar_equilibrium_test_utils::reduce_density(f, densityTrue) + ); + + stellar_equilibrium_test_utils::assign_value_block( + analyticState, layout, stellar_equilibrium_test_utils::gravityGradientValue, gravityGradientTrue + ); + + stellar_equilibrium_test_utils::assign_value_block( + analyticState, layout, stellar_equilibrium_test_utils::gravityPotentialValue, gravityPotentialTrue + ); + + stellar_equilibrium_test_utils::assign_value_block( + analyticState, layout, stellar_equilibrium_test_utils::enthalpyValue, + stellar_equilibrium_test_utils::reduce_enthalpy(f, enthalpyTrue) + ); + + stellar_equilibrium_test_utils::value_view(analyticState, layout, stellar_equilibrium_test_utils::bernoulliValue)( + 0 + ) = bernoulliConstant; + + mean_field::operators::StellarEquilibriumDependencies dependencies = + stellar_equilibrium_test_utils::make_dependencies(); + + const mean_field::physics::RigidRotation zeroRotation = stellar_equilibrium_test_utils::make_zero_rotation(); + + stellarOperator.Prepare(analyticState, dependencies, zeroRotation); + + mfem::Vector analyticResidual; + stellarOperator.BuildResidual(analyticResidual); + + const double analyticGradientNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + analyticResidual, layout, stellar_equilibrium_test_utils::gravityGradientResidual + ), + f.mesh->GetComm() + ); + + const double analyticPoissonNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + analyticResidual, layout, stellar_equilibrium_test_utils::gravityPotentialResidual + ), + f.mesh->GetComm() + ); + + const double analyticClosureNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + analyticResidual, layout, stellar_equilibrium_test_utils::densityResidual + ), + f.mesh->GetComm() + ); + + const double analyticDisplacementNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + analyticResidual, layout, stellar_equilibrium_test_utils::displacementResidual + ), + f.mesh->GetComm() + ); + + const double analyticHydrostaticNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + analyticResidual, layout, stellar_equilibrium_test_utils::enthalpyResidual + ), + f.mesh->GetComm() + ); + + const double analyticMassError = std::abs( + stellar_equilibrium_test_utils:: + const_residual_view(analyticResidual, layout, stellar_equilibrium_test_utils::massResidual)(0) + ); + + /* + * Construct a deliberately inconsistent nearby state. The analytic + * projection should have a substantially smaller residual in every row. + */ + mfem::Vector perturbedState(analyticState); + + { + mfem::Vector block = stellar_equilibrium_test_utils::value_view( + perturbedState, layout, stellar_equilibrium_test_utils::densityValue + ); + block *= 1.12; + } + + { + mfem::Vector block = stellar_equilibrium_test_utils::value_view( + perturbedState, layout, stellar_equilibrium_test_utils::gravityGradientValue + ); + block *= 0.87; + } + + { + mfem::Vector block = stellar_equilibrium_test_utils::value_view( + perturbedState, layout, stellar_equilibrium_test_utils::gravityPotentialValue + ); + block *= 1.08; + } + + { + mfem::Vector block = stellar_equilibrium_test_utils::value_view( + perturbedState, layout, stellar_equilibrium_test_utils::enthalpyValue + ); + block *= 0.91; + } + + stellar_equilibrium_test_utils::value_view(perturbedState, layout, stellar_equilibrium_test_utils::bernoulliValue)( + 0 + ) *= 1.04; + + stellar_equilibrium_test_utils::increment_all_state_revisions(dependencies); + + stellarOperator.Prepare(perturbedState, dependencies, zeroRotation); + + mfem::Vector perturbedResidual; + stellarOperator.BuildResidual(perturbedResidual); + + const double perturbedGradientNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + perturbedResidual, layout, stellar_equilibrium_test_utils::gravityGradientResidual + ), + f.mesh->GetComm() + ); + + const double perturbedPoissonNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + perturbedResidual, layout, stellar_equilibrium_test_utils::gravityPotentialResidual + ), + f.mesh->GetComm() + ); + + const double perturbedClosureNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + perturbedResidual, layout, stellar_equilibrium_test_utils::densityResidual + ), + f.mesh->GetComm() + ); + + const double perturbedDisplacementNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + perturbedResidual, layout, stellar_equilibrium_test_utils::displacementResidual + ), + f.mesh->GetComm() + ); + + const double perturbedHydrostaticNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + perturbedResidual, layout, stellar_equilibrium_test_utils::enthalpyResidual + ), + f.mesh->GetComm() + ); + + const double perturbedMassError = std::abs( + stellar_equilibrium_test_utils:: + const_residual_view(perturbedResidual, layout, stellar_equilibrium_test_utils::massResidual)(0) + ); + + INFO("Analytic n=1 central density = " << centralDensity); + INFO("Analytic n=1 polytropic constant = " << polytropicConstant); + INFO("Analytic n=1 target mass = " << targetMass); + INFO("Analytic n=1 Bernoulli constant = " << bernoulliConstant); + + INFO("Gravity-gradient residual: analytic = " << analyticGradientNorm << ", perturbed = " << perturbedGradientNorm); + + INFO("Poisson residual: analytic = " << analyticPoissonNorm << ", perturbed = " << perturbedPoissonNorm); + + INFO("Closure residual: analytic = " << analyticClosureNorm << ", perturbed = " << perturbedClosureNorm); + + INFO( + "Displacement residual: analytic = " << analyticDisplacementNorm + << ", perturbed = " << perturbedDisplacementNorm + ); + + INFO( + "Hydrostatic residual: analytic = " << analyticHydrostaticNorm << ", perturbed = " << perturbedHydrostaticNorm + ); + + INFO("Mass error: analytic = " << analyticMassError << ", perturbed = " << perturbedMassError); + + REQUIRE(std::isfinite(perturbedGradientNorm)); + REQUIRE(perturbedPoissonNorm > 0.0); + REQUIRE(perturbedClosureNorm > 0.0); + REQUIRE(perturbedDisplacementNorm > 0.0); + REQUIRE(perturbedHydrostaticNorm > 0.0); + REQUIRE(perturbedMassError > 0.0); + + /* + * Closure and hydrostatic balance are algebraically especially favorable + * for n = 1 because h = 2 K rho and h + Phi = C are linear relations. + */ + CHECK(analyticClosureNorm < 0.10 * perturbedClosureNorm); + + CHECK(analyticHydrostaticNorm < 0.10 * perturbedHydrostaticNorm); + + /* + * Phi_h and g_h are independent L2 and RT projections of the analytic + * potential and gradient. They are not a commuting mixed projection and + * therefore need not satisfy + * + * M_g g_h + B^T Phi_h = 0. + * + * The resulting R_g value is a finite-element projection-compatibility + * floor, not a physical equilibrium error. Gravity solver-to-projection + * accuracy is tested independently by the dedicated gravity tests. + */ + CHECK(std::isfinite(analyticGradientNorm)); + CHECK(analyticPoissonNorm < 0.35 * perturbedPoissonNorm); + + CHECK(analyticDisplacementNorm < 0.35 * perturbedDisplacementNorm); + + CHECK(analyticMassError < 5.0e-5 * targetMass); + + CHECK(analyticMassError < 0.10 * perturbedMassError); +} + +TEST_CASE( + "Prepared Stellar Equilibrium Has A Restoring Jacobian Around An N3 Polytrope", + tags::barotrope &tags::prepared &tags::analytic_comparison &tags::accuracy &tags::gravity &tags::hydro + &tags::jacobian &tags::convergence +) { + mean_field::utils::Args args = test_utils::setup_args(); + + args.p.rtol = 1.0e-12; + args.p.max_iters = std::max(args.p.max_iters, 1000); + + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); + + REQUIRE(f.okay()); + REQUIRE(f.mapping != nullptr); + REQUIRE(f.domainMapperStateless != nullptr); + + const double pi = std::acos(-1.0); + const double stellarRadius = mean_field::utils::RADIUS; + const double targetMass = mean_field::utils::MASS; + + /* + * Standard n = 3 Lane-Emden constants: + * + * xi_1 = 6.896848619... + * -xi_1^2 theta'(xi_1) = 2.018235951... + */ + constexpr double surfaceCoordinate = 6.8968486193769603755; + + constexpr double dimensionlessMass = 2.0182359509662283534; + + /* + * For n = 3, + * + * M = 4 pi (K / (pi G))^(3/2) mu_1. + * + * This fixes K for the requested target mass. + */ + const double polytropicConstant = + pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0); + + /* + * The n = 3 radius is + * + * R = xi_1 sqrt(K / (pi G)) rho_c^(-1/3). + * + * Choose rho_c so that the Lane-Emden surface coincides with the + * stellar boundary of the test mesh. + */ + const double centralDensity = + std::pow(surfaceCoordinate * std::sqrt(polytropicConstant / (pi * mean_field::utils::G)) / stellarRadius, 3.0); + + const mean_field::eos::Polytrope equationOfState(3.0, polytropicConstant); + + const mean_field::models::structure::PolytropicStructure structurePrescription(equationOfState, targetMass); + + const mean_field::models::structure::StructureSeed seed = + structurePrescription.makeInitialSeed({.centralDensity = centralDensity, .radialSampleCount = 8192}); + + INFO("Requested stellar radius = " << stellarRadius); + INFO("Seed stellar radius = " << seed.stellarRadius); + INFO("Target mass = " << targetMass); + INFO("Polytropic constant = " << polytropicConstant); + INFO("Central density = " << centralDensity); + + REQUIRE(seed.radius.Size() == seed.density.Size()); + REQUIRE(seed.radius.Size() == seed.enthalpy.Size()); + REQUIRE(seed.radius.Size() == 8192); + + CHECK(std::abs(seed.stellarRadius - stellarRadius) / stellarRadius < 2.0e-4); + + const auto interpolateProfile = [](const mfem::Vector &radiusSamples, const mfem::Vector &valueSamples, + const double radius) { + MFEM_VERIFY(radiusSamples.Size() == valueSamples.Size(), "The radial profile has inconsistent sample sizes."); + + MFEM_VERIFY(radiusSamples.Size() >= 2, "The radial profile requires at least two samples."); + + if (radius <= radiusSamples(0)) { + return valueSamples(0); + } + + const int finalIndex = radiusSamples.Size() - 1; + + if (radius >= radiusSamples(finalIndex)) { + return valueSamples(finalIndex); + } + + int lowerIndex = 0; + int upperIndex = finalIndex; + + while (upperIndex - lowerIndex > 1) { + const int middleIndex = lowerIndex + (upperIndex - lowerIndex) / 2; + + if (radiusSamples(middleIndex) <= radius) { + lowerIndex = middleIndex; + } else { + upperIndex = middleIndex; + } + } + + const double radialInterval = radiusSamples(upperIndex) - radiusSamples(lowerIndex); + + MFEM_VERIFY(radialInterval > 0.0, "The radial profile is not strictly increasing."); + + const double fraction = (radius - radiusSamples(lowerIndex)) / radialInterval; + + return (1.0 - fraction) * valueSamples(lowerIndex) + fraction * valueSamples(upperIndex); + }; + + mfem::FunctionCoefficient densityCoefficient([&seed, &interpolateProfile](const mfem::Vector &position) { + const double radius = position.Norml2(); + + if (radius >= seed.stellarRadius) { + return 0.0; + } + + return interpolateProfile(seed.radius, seed.density, radius); + }); + + mfem::FunctionCoefficient enthalpyCoefficient([&seed, &interpolateProfile](const mfem::Vector &position) { + const double radius = position.Norml2(); + + if (radius >= seed.stellarRadius) { + return 0.0; + } + + return interpolateProfile(seed.radius, seed.enthalpy, radius); + }); + + mfem::ParGridFunction densityField(f.densityFes.get()); + mfem::ParGridFunction enthalpyField(f.enthalpyFes.get()); + mfem::ParGridFunction displacementField(f.displacementFes.get()); + + densityField = 0.0; + enthalpyField = 0.0; + displacementField = 0.0; + + densityField.ProjectCoefficient(densityCoefficient); + enthalpyField.ProjectCoefficient(enthalpyCoefficient); + + /* + * Gravity initialization and the prepared root operator must see the + * same undeformed geometry. + */ + f.mapping->SetDisplacement(displacementField); + mean_field::physics::update_stiffness_matrix(f); + + const mean_field::physics::GravitySolution gravitySolution = + mean_field::physics::grav_potential_new(f, args, densityField, displacementField); + + mfem::Vector densityTrue; + mfem::Vector enthalpyTrue; + mfem::Vector displacementTrue; + mfem::Vector gravityGradientTrue; + mfem::Vector gravityPotentialTrue; + + densityField.GetTrueDofs(densityTrue); + enthalpyField.GetTrueDofs(enthalpyTrue); + displacementField.GetTrueDofs(displacementTrue); + gravitySolution.gradPhi.GetTrueDofs(gravityGradientTrue); + gravitySolution.phi.GetTrueDofs(gravityPotentialTrue); + + const double bernoulliConstant = -mean_field::utils::G * targetMass / stellarRadius; + + const mean_field::eos::Polytrope barotrope(3.0, polytropicConstant); + + mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator( + f, *f.domainMapperStateless, barotrope, targetMass + ); + + const mean_field::operators::StellarEquilibriumLayout &layout = stellarOperator.GetLayout(); + + mfem::Vector equilibriumState(layout.value_offsets().Last()); + equilibriumState = 0.0; + + stellar_equilibrium_test_utils::assign_value_block( + equilibriumState, layout, stellar_equilibrium_test_utils::densityValue, + stellar_equilibrium_test_utils::reduce_density(f, densityTrue) + ); + + stellar_equilibrium_test_utils::assign_value_block( + equilibriumState, layout, stellar_equilibrium_test_utils::displacementValue, displacementTrue + ); + + stellar_equilibrium_test_utils::assign_value_block( + equilibriumState, layout, stellar_equilibrium_test_utils::gravityGradientValue, gravityGradientTrue + ); + + stellar_equilibrium_test_utils::assign_value_block( + equilibriumState, layout, stellar_equilibrium_test_utils::gravityPotentialValue, gravityPotentialTrue + ); + + stellar_equilibrium_test_utils::assign_value_block( + equilibriumState, layout, stellar_equilibrium_test_utils::enthalpyValue, + stellar_equilibrium_test_utils::reduce_enthalpy(f, enthalpyTrue) + ); + + stellar_equilibrium_test_utils:: + value_view(equilibriumState, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) = bernoulliConstant; + + mean_field::operators::StellarEquilibriumDependencies dependencies = + stellar_equilibrium_test_utils::make_dependencies(); + + const mean_field::physics::RigidRotation zeroRotation = stellar_equilibrium_test_utils::make_zero_rotation(); + + stellarOperator.Prepare(equilibriumState, dependencies, zeroRotation); + + mfem::Vector equilibriumResidual; + stellarOperator.BuildResidual(equilibriumResidual); + + /* + * Construct a physically safe perturbation direction. Density and + * enthalpy perturbations vanish at the surface because they are + * proportional to the equilibrium profiles. + */ + mfem::Vector perturbationDirection(layout.value_offsets().Last()); + perturbationDirection = 0.0; + + mfem::Vector densityDirection(densityTrue); + densityDirection *= 0.12; + + mfem::Vector gravityGradientDirection(gravityGradientTrue); + gravityGradientDirection *= -0.09; + + mfem::Vector gravityPotentialDirection(gravityPotentialTrue); + gravityPotentialDirection *= 0.07; + + mfem::Vector enthalpyDirection(enthalpyTrue); + enthalpyDirection *= -0.11; + + const mfem::Vector displacementDirection = stellar_equilibrium_test_utils::project_displacement_direction(f, 0.15); + + stellar_equilibrium_test_utils::assign_value_block( + perturbationDirection, layout, stellar_equilibrium_test_utils::densityValue, + stellar_equilibrium_test_utils::reduce_density(f, densityDirection) + ); + + stellar_equilibrium_test_utils::assign_value_block( + perturbationDirection, layout, stellar_equilibrium_test_utils::displacementValue, displacementDirection + ); + + stellar_equilibrium_test_utils::assign_value_block( + perturbationDirection, layout, stellar_equilibrium_test_utils::gravityGradientValue, gravityGradientDirection + ); + + stellar_equilibrium_test_utils::assign_value_block( + perturbationDirection, layout, stellar_equilibrium_test_utils::gravityPotentialValue, gravityPotentialDirection + ); + + stellar_equilibrium_test_utils::assign_value_block( + perturbationDirection, layout, stellar_equilibrium_test_utils::enthalpyValue, + stellar_equilibrium_test_utils::reduce_enthalpy(f, enthalpyDirection) + ); + + stellar_equilibrium_test_utils:: + value_view(perturbationDirection, layout, stellar_equilibrium_test_utils::bernoulliValue)(0) = + 0.05 * bernoulliConstant; + + /* + * Evaluate J delta-x at the equilibrium state before changing the + * prepared base point. + */ + mfem::Vector jacobianAction; + + stellarOperator.Mult(perturbationDirection, jacobianAction); + + constexpr double perturbationScale = 2.0e-2; + + mfem::Vector perturbedState(equilibriumState); + perturbedState.Add(perturbationScale, perturbationDirection); + + stellar_equilibrium_test_utils::increment_all_state_revisions(dependencies); + + stellarOperator.Prepare(perturbedState, dependencies, zeroRotation); + + mfem::Vector perturbedResidual; + stellarOperator.BuildResidual(perturbedResidual); + + const auto residualBlockNorm = [&layout, &f](const mfem::Vector &residual, const auto block) { + return stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view(residual, layout, block), f.mesh->GetComm() + ); + }; + + const std::array equilibriumRowNorms{ + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::gravityGradientResidual), + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::gravityPotentialResidual), + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::densityResidual), + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::displacementResidual), + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::enthalpyResidual), + residualBlockNorm(equilibriumResidual, stellar_equilibrium_test_utils::massResidual) + }; + + const std::array perturbedRowNorms{ + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::gravityGradientResidual), + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::gravityPotentialResidual), + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::densityResidual), + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::displacementResidual), + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::enthalpyResidual), + residualBlockNorm(perturbedResidual, stellar_equilibrium_test_utils::massResidual) + }; + + constexpr std::array rowNames{"gravity-gradient", "Poisson", "closure", + "displacement", "hydrostatic", "mass"}; + + /* + * Most rows are close to exact discrete relations. The displacement row + * combines independently projected thermodynamic fields with the discrete + * gravity solution and consequently has a larger force-balance projection + * floor. + */ + constexpr std::array maximumEquilibriumFractions{ + 0.35, // gravity-gradient + 0.35, // Poisson + 0.35, // closure + 0.60, // displacement-force balance + 0.35, // hydrostatic + 0.35 // mass + }; + + for (int row = 0; row < 6; ++row) { + CAPTURE(row); + CAPTURE(rowNames[row]); + CAPTURE(equilibriumRowNorms[row]); + CAPTURE(perturbedRowNorms[row]); + CAPTURE(maximumEquilibriumFractions[row]); + + REQUIRE(std::isfinite(equilibriumRowNorms[row])); + REQUIRE(std::isfinite(perturbedRowNorms[row])); + REQUIRE(perturbedRowNorms[row] > 0.0); + + /* + * The Lane-Emden state must be closer to equilibrium than the nearby + * perturbed state in every residual row. + */ + CHECK(equilibriumRowNorms[row] < perturbedRowNorms[row]); + + /* + * Require a substantial separation from the perturbed state while + * allowing the larger discrete projection floor in the force row. + */ + CHECK(equilibriumRowNorms[row] < maximumEquilibriumFractions[row] * perturbedRowNorms[row]); + } + + /* + * Record an absolute regression bound for the current coarse-mesh + * displacement-force projection floor. + */ + CHECK(equilibriumRowNorms[3] < 1.0e-3); + + const double equilibriumMassError = std::abs( + stellar_equilibrium_test_utils:: + const_residual_view(equilibriumResidual, layout, stellar_equilibrium_test_utils::massResidual)(0) + ); + + INFO("Equilibrium relative mass error = " << equilibriumMassError / targetMass); + + CHECK(equilibriumMassError < 5.0e-4 * targetMass); + + /* + * The nonlinear residual departure should be + * + * R(x + epsilon p) - R(x) + * = epsilon J(x) p + O(epsilon^2). + */ + mfem::Vector residualDeparture(perturbedResidual); + residualDeparture -= equilibriumResidual; + + mfem::Vector linearizedDeparture(jacobianAction); + linearizedDeparture *= perturbationScale; + + mfem::Vector nonlinearRemainder(residualDeparture); + nonlinearRemainder -= linearizedDeparture; + + const double departureNorm = stellar_equilibrium_test_utils::global_norm(residualDeparture, f.mesh->GetComm()); + + const double nonlinearRemainderNorm = + stellar_equilibrium_test_utils::global_norm(nonlinearRemainder, f.mesh->GetComm()); + + /* + * Apply the known restoring correction -epsilon p through the Jacobian. + * + * This predicts the residual after returning to the equilibrium state: + * + * R(x + epsilon p) - epsilon J(x)p approximately R(x). + */ + mfem::Vector restoredResidualPrediction(perturbedResidual); + restoredResidualPrediction.Add(-perturbationScale, jacobianAction); + + restoredResidualPrediction -= equilibriumResidual; + + const double restoredDistance = + stellar_equilibrium_test_utils::global_norm(restoredResidualPrediction, f.mesh->GetComm()); + + INFO("Residual departure norm = " << departureNorm); + INFO("Nonlinear remainder norm = " << nonlinearRemainderNorm); + INFO("Distance after the restoring Jacobian correction = " << restoredDistance); + INFO("Relative first-order remainder = " << nonlinearRemainderNorm / departureNorm); + + REQUIRE(std::isfinite(departureNorm)); + REQUIRE(std::isfinite(nonlinearRemainderNorm)); + REQUIRE(std::isfinite(restoredDistance)); + REQUIRE(departureNorm > 0.0); + + CHECK(nonlinearRemainderNorm < 5.0e-2 * departureNorm); + + CHECK(restoredDistance < 5.0e-2 * departureNorm); + + /* + * Rotational shape response + * + * At moderate rotation, the leading deformation is a smooth, axisymmetric, + * approximately quadrupolar oblateness. A convenient volume-preserving + * affine representative is + * + * delta d(X) = (X, Y, -2 Z). + * + * It moves the equator outward, moves the poles inward, and has zero trace. + * A cusp is not expected until the nonlinear solution approaches mass + * shedding. + */ + { + const double keplerianAngularSpeed = + std::sqrt(mean_field::utils::G * targetMass / (stellarRadius * stellarRadius * stellarRadius)); + + constexpr double rotationFraction = 0.50; + const double angularSpeed = rotationFraction * keplerianAngularSpeed; + + mfem::Vector angularVelocity(3); + angularVelocity = 0.0; + angularVelocity(2) = angularSpeed; + + mfem::Vector rotationCenter(3); + rotationCenter = 0.0; + + const mean_field::physics::RigidRotation rotation(angularVelocity, rotationCenter); + + /* + * Return from the perturbed state used by the preceding Jacobian test to + * the spherical equilibrium state, while changing the rotation stream. + */ + stellar_equilibrium_test_utils::increment_all_state_revisions(dependencies); + ++dependencies.rotation.revision; + + stellarOperator.Prepare(equilibriumState, dependencies, rotation); + + mfem::Vector rotatingSphericalResidual; + stellarOperator.BuildResidual(rotatingSphericalResidual); + + mfem::ParGridFunction oblateDisplacementField(f.displacementFes.get()); + + mfem::VectorFunctionCoefficient oblateDisplacementCoefficient( + f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + + /* + * Positive amplitude: + * + * equator: d = (x, y, 0), outward + * pole: d = (0, 0, -2 z), inward + * + * The displacement gradient has trace 1 + 1 - 2 = 0, so this is + * volume preserving to first order. + */ + value(0) = position(0); + value(1) = position(1); + value(2) = -2.0 * position(2); + } + ); + + oblateDisplacementField = 0.0; + oblateDisplacementField.ProjectCoefficient(oblateDisplacementCoefficient); + + mfem::Vector oblateDisplacement; + oblateDisplacementField.GetTrueDofs(oblateDisplacement); + + mfem::Vector oblateDirection(layout.value_offsets().Last()); + oblateDirection = 0.0; + + stellar_equilibrium_test_utils::assign_value_block( + oblateDirection, layout, stellar_equilibrium_test_utils::displacementValue, oblateDisplacement + ); + + const mfem::Vector equilibriumDisplacementResidual = stellar_equilibrium_test_utils::const_residual_view( + equilibriumResidual, layout, stellar_equilibrium_test_utils::displacementResidual + ); + + const mfem::Vector rotatingDisplacementResidual = stellar_equilibrium_test_utils::const_residual_view( + rotatingSphericalResidual, layout, stellar_equilibrium_test_utils::displacementResidual + ); + + /* + * Subtract the nonrotating force-balance projection floor. The remainder + * is the displacement residual introduced by rotation. + */ + mfem::Vector rotationInducedResidual(rotatingDisplacementResidual); + rotationInducedResidual -= equilibriumDisplacementResidual; + + const double rotationInducedWork = + gravity_prepared_test_utils::global_dot(rotationInducedResidual, oblateDisplacement, f.mesh->GetComm()); + + const double rotationInducedNorm = + stellar_equilibrium_test_utils::global_norm(rotationInducedResidual, f.mesh->GetComm()); + + const double oblateDirectionNorm = + stellar_equilibrium_test_utils::global_norm(oblateDisplacement, f.mesh->GetComm()); + + const double workScale = rotationInducedNorm * oblateDirectionNorm; + + INFO("Keplerian angular speed = " << keplerianAngularSpeed); + INFO("Applied angular speed = " << angularSpeed); + INFO("Rotation fraction = " << rotationFraction); + INFO("Rotation-induced displacement residual norm = " << rotationInducedNorm); + INFO("Rotation-induced work against the oblate direction = " << rotationInducedWork); + INFO("Normalized oblate work = " << rotationInducedWork / workScale); + + REQUIRE(std::isfinite(rotationInducedWork)); + REQUIRE(std::isfinite(rotationInducedNorm)); + REQUIRE(std::isfinite(oblateDirectionNorm)); + REQUIRE(rotationInducedNorm > 0.0); + REQUIRE(oblateDirectionNorm > 0.0); + REQUIRE(workScale > 0.0); + + /* + * The force residual uses the convention R_rot(w) = -integral rho a_c.w. + * Therefore negative work against this direction means that -R, the + * Newton right-hand side, drives a positive oblate deformation. + */ + CHECK(rotationInducedWork < 0.0); + + CHECK(rotationInducedWork < -1.0e-3 * workScale); + + /* + * Evaluate the displacement column of the complete coupled Jacobian at + * the rotating spherical state. + */ + mfem::Vector oblateJacobianAction; + stellarOperator.Mult(oblateDirection, oblateJacobianAction); + + const mfem::Vector oblateDisplacementJacobianAction = stellar_equilibrium_test_utils::const_residual_view( + oblateJacobianAction, layout, stellar_equilibrium_test_utils::displacementResidual + ); + + const double residualDirectionalDerivative = gravity_prepared_test_utils::global_dot( + rotatingDisplacementResidual, oblateDisplacementJacobianAction, f.mesh->GetComm() + ); + + const double jacobianDirectionNormSquared = gravity_prepared_test_utils::global_dot( + oblateDisplacementJacobianAction, oblateDisplacementJacobianAction, f.mesh->GetComm() + ); + + REQUIRE(std::isfinite(residualDirectionalDerivative)); + REQUIRE(std::isfinite(jacobianDirectionNormSquared)); + REQUIRE(jacobianDirectionNormSquared > 0.0); + + /* + * Minimize the linearized displacement-residual norm along the oblate + * direction: + * + * alpha_* = -(R_d, J_d p) / ||J_d p||^2. + * + * A positive alpha_* means that the operator selects equatorial expansion + * and polar contraction rather than the prolate direction. + */ + const double optimalLinearizedAmplitude = -residualDirectionalDerivative / jacobianDirectionNormSquared; + + INFO("Displacement-residual directional derivative = " << residualDirectionalDerivative); + INFO("Optimal linearized oblate amplitude = " << optimalLinearizedAmplitude); + + REQUIRE(std::isfinite(optimalLinearizedAmplitude)); + CHECK(residualDirectionalDerivative < 0.0); + REQUIRE(optimalLinearizedAmplitude > 0.0); + + /* + * Take only a fraction of the predicted step and cap it at a two-percent + * surface deformation. This keeps the test safely inside the local + * linearization regime. + */ + const double appliedOblateAmplitude = std::min(0.25 * optimalLinearizedAmplitude, 2.0e-2); + + REQUIRE(appliedOblateAmplitude > 0.0); + + mfem::Vector predictedDisplacementResidual(rotatingDisplacementResidual); + predictedDisplacementResidual.Add(appliedOblateAmplitude, oblateDisplacementJacobianAction); + + const double rotatingDisplacementNorm = + stellar_equilibrium_test_utils::global_norm(rotatingDisplacementResidual, f.mesh->GetComm()); + + const double predictedDisplacementNorm = + stellar_equilibrium_test_utils::global_norm(predictedDisplacementResidual, f.mesh->GetComm()); + + INFO("Rotating spherical displacement residual norm = " << rotatingDisplacementNorm); + INFO("Predicted oblate displacement residual norm = " << predictedDisplacementNorm); + + CHECK(predictedDisplacementNorm < rotatingDisplacementNorm); + + /* + * Apply the same positive oblate displacement to the nonlinear operator. + * Only the displacement row is compared: a complete rotating equilibrium + * also requires simultaneous changes in rho, g, Phi, h, and C. + */ + mfem::Vector oblateState(equilibriumState); + + { + mfem::Vector displacementBlock = stellar_equilibrium_test_utils::value_view( + oblateState, layout, stellar_equilibrium_test_utils::displacementValue + ); + + displacementBlock.Add(appliedOblateAmplitude, oblateDisplacement); + } + + ++dependencies.displacement.revision; + + stellarOperator.Prepare(oblateState, dependencies, rotation); + + mfem::Vector nonlinearOblateResidual; + stellarOperator.BuildResidual(nonlinearOblateResidual); + + const double nonlinearOblateDisplacementNorm = stellar_equilibrium_test_utils::global_norm( + stellar_equilibrium_test_utils::const_residual_view( + nonlinearOblateResidual, layout, stellar_equilibrium_test_utils::displacementResidual + ), + f.mesh->GetComm() + ); + + const double equatorialRadiusScale = 1.0 + appliedOblateAmplitude; + + const double polarRadiusScale = 1.0 - 2.0 * appliedOblateAmplitude; + + const double equatorialToPolarRadiusRatio = equatorialRadiusScale / polarRadiusScale; + + INFO("Applied oblate amplitude = " << appliedOblateAmplitude); + INFO("Nonlinear oblate displacement residual norm = " << nonlinearOblateDisplacementNorm); + INFO("Equatorial radius scale = " << equatorialRadiusScale); + INFO("Polar radius scale = " << polarRadiusScale); + INFO("Equatorial-to-polar radius ratio = " << equatorialToPolarRadiusRatio); + + CHECK(equatorialRadiusScale > 1.0); + CHECK(polarRadiusScale < 1.0); + CHECK(polarRadiusScale > 0.0); + CHECK(equatorialToPolarRadiusRatio > 1.0); + + CHECK(nonlinearOblateDisplacementNorm < rotatingDisplacementNorm); + } +} diff --git a/tests/physics/barotrope.cpp b/tests/physics/barotrope.cpp index 63999e9..5fc4d61 100644 --- a/tests/physics/barotrope.cpp +++ b/tests/physics/barotrope.cpp @@ -15,42 +15,26 @@ TEST_CASE( constexpr double polytropic_index = 3.0; constexpr double polytropic_constant = 1.5; - const mean_field::physics::PolytropicBarotrope barotrope( - polytropic_index, polytropic_constant - ); + const mean_field::physics::PolytropicBarotrope barotrope(polytropic_index, polytropic_constant); const std::array densities{1.0e-6, 1.0e-3, 0.1, 0.7, 2.0}; for (const double density : densities) { - const double pressure = barotrope.pressure_from_density(density); + const double pressure = barotrope.pressure_from_density(density); - const double enthalpy = barotrope.enthalpy_from_density(density); + const double enthalpy = barotrope.enthalpy_from_density(density); - const double reconstructed_density = - barotrope.density_from_enthalpy(enthalpy); + const double reconstructed_density = barotrope.density_from_enthalpy(enthalpy); - const double reconstructed_pressure = - barotrope.pressure_from_enthalpy(enthalpy); + const double reconstructed_pressure = barotrope.pressure_from_enthalpy(enthalpy); - CHECK_THAT( - reconstructed_density, Catch::Matchers::WithinRel(density, 2.0e-14) - ); + CHECK_THAT(reconstructed_density, Catch::Matchers::WithinRel(density, 2.0e-14)); - CHECK_THAT( - reconstructed_pressure, - Catch::Matchers::WithinRel(pressure, 2.0e-14) - ); + CHECK_THAT(reconstructed_pressure, Catch::Matchers::WithinRel(pressure, 2.0e-14)); - CHECK_THAT( - pressure, Catch::Matchers::WithinRel( - density * enthalpy / (polytropic_index + 1.0), 2.0e-14 - ) - ); + CHECK_THAT(pressure, Catch::Matchers::WithinRel(density * enthalpy / (polytropic_index + 1.0), 2.0e-14)); - CHECK_THAT( - barotrope.pressure_derivative_from_enthalpy(enthalpy), - Catch::Matchers::WithinRel(density, 2.0e-14) - ); + CHECK_THAT(barotrope.pressure_derivative_from_enthalpy(enthalpy), Catch::Matchers::WithinRel(density, 2.0e-14)); CHECK_THAT( barotrope.pressure_derivative_from_density(density), @@ -71,27 +55,21 @@ TEST_CASE( const double step = 1.0e-6 * std::max(1.0, enthalpy); const double density_difference = - (barotrope.density_from_enthalpy(enthalpy + step) - - barotrope.density_from_enthalpy(enthalpy - step)) / + (barotrope.density_from_enthalpy(enthalpy + step) - barotrope.density_from_enthalpy(enthalpy - step)) / (2.0 * step); const double pressure_difference = - (barotrope.pressure_from_enthalpy(enthalpy + step) - - barotrope.pressure_from_enthalpy(enthalpy - step)) / + (barotrope.pressure_from_enthalpy(enthalpy + step) - barotrope.pressure_from_enthalpy(enthalpy - step)) / (2.0 * step); CHECK_THAT( density_difference, - Catch::Matchers::WithinRel( - barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10 - ) + Catch::Matchers::WithinRel(barotrope.density_derivative_from_enthalpy(enthalpy), 5.0e-10) ); CHECK_THAT( pressure_difference, - Catch::Matchers::WithinRel( - barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10 - ) + Catch::Matchers::WithinRel(barotrope.pressure_derivative_from_enthalpy(enthalpy), 5.0e-10) ); } } @@ -119,21 +97,12 @@ TEST_CASE( "Polytropic Barotrope Rejects Invalid Material Parameters", tags::hydro &tags::unit ) { - CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope(0.5, 1.0), - std::invalid_argument - ); + CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(0.5, 1.0), std::invalid_argument); + + CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, 0.0), std::invalid_argument); CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope(3.0, 0.0), - std::invalid_argument - ); - - CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope( - std::numeric_limits::infinity(), 1.0 - ), - std::invalid_argument + mean_field::physics::PolytropicBarotrope(std::numeric_limits::infinity(), 1.0), std::invalid_argument ); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 1.0); diff --git a/tests/physics/barotrope_pressure.cpp b/tests/physics/barotrope_pressure.cpp index a5fd127..4a41231 100644 --- a/tests/physics/barotrope_pressure.cpp +++ b/tests/physics/barotrope_pressure.cpp @@ -20,8 +20,7 @@ namespace polytropic_barotrope_test_utils { const double position, const double step ) { - return (function(position + step) - function(position - step)) / - (2.0 * step); + return (function(position + step) - function(position - step)) / (2.0 * step); } template @@ -31,10 +30,8 @@ namespace polytropic_barotrope_test_utils { ) { double integral = 0.0; - for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); - ++pointIndex) { - const mfem::IntegrationPoint &integrationPoint = - integrationRule.IntPoint(pointIndex); + for (int pointIndex = 0; pointIndex < integrationRule.GetNPoints(); ++pointIndex) { + const mfem::IntegrationPoint &integrationPoint = integrationRule.IntPoint(pointIndex); integral += integrationPoint.weight * integrand(integrationPoint); } @@ -55,12 +52,9 @@ TEST_CASE( for (const double polytropicIndex : polytropicIndices) { DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { - const mean_field::physics::PolytropicBarotrope barotrope( - polytropicIndex, polytropicConstant - ); + const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); - const double expectedEnthalpyScale = - (polytropicIndex + 1.0) * polytropicConstant; + const double expectedEnthalpyScale = (polytropicIndex + 1.0) * polytropicConstant; CHECK(barotrope.polytropic_index() == polytropicIndex); @@ -71,45 +65,25 @@ TEST_CASE( for (const double density : densities) { CAPTURE(polytropicIndex, polytropicConstant, density); - const double expectedPressure = - polytropicConstant * - std::pow(density, 1.0 + 1.0 / polytropicIndex); + const double expectedPressure = polytropicConstant * std::pow(density, 1.0 + 1.0 / polytropicIndex); - const double expectedEnthalpy = - expectedEnthalpyScale * - std::pow(density, 1.0 / polytropicIndex); + const double expectedEnthalpy = expectedEnthalpyScale * std::pow(density, 1.0 / polytropicIndex); - const double pressureFromDensity = - barotrope.pressure_from_density(density); + const double pressureFromDensity = barotrope.pressure_from_density(density); - const double enthalpyFromDensity = - barotrope.enthalpy_from_density(density); + const double enthalpyFromDensity = barotrope.enthalpy_from_density(density); - const double recoveredDensity = - barotrope.density_from_enthalpy(enthalpyFromDensity); + const double recoveredDensity = barotrope.density_from_enthalpy(enthalpyFromDensity); - const double pressureFromEnthalpy = - barotrope.pressure_from_enthalpy(enthalpyFromDensity); + const double pressureFromEnthalpy = barotrope.pressure_from_enthalpy(enthalpyFromDensity); - CHECK_THAT( - pressureFromDensity, - Catch::Matchers::WithinRel(expectedPressure, 2.0e-13) - ); + CHECK_THAT(pressureFromDensity, Catch::Matchers::WithinRel(expectedPressure, 2.0e-13)); - CHECK_THAT( - enthalpyFromDensity, - Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13) - ); + CHECK_THAT(enthalpyFromDensity, Catch::Matchers::WithinRel(expectedEnthalpy, 2.0e-13)); - CHECK_THAT( - recoveredDensity, - Catch::Matchers::WithinRel(density, 5.0e-13) - ); + CHECK_THAT(recoveredDensity, Catch::Matchers::WithinRel(density, 5.0e-13)); - CHECK_THAT( - pressureFromEnthalpy, - Catch::Matchers::WithinRel(expectedPressure, 5.0e-13) - ); + CHECK_THAT(pressureFromEnthalpy, Catch::Matchers::WithinRel(expectedPressure, 5.0e-13)); /* * Polytropic identity: @@ -118,10 +92,7 @@ TEST_CASE( */ CHECK_THAT( pressureFromEnthalpy, - Catch::Matchers::WithinRel( - density * enthalpyFromDensity / (polytropicIndex + 1.0), - 5.0e-13 - ) + Catch::Matchers::WithinRel(density * enthalpyFromDensity / (polytropicIndex + 1.0), 5.0e-13) ); /* @@ -133,9 +104,8 @@ TEST_CASE( * value as density_from_enthalpy(). */ CHECK( - barotrope.pressure_derivative_from_enthalpy( - enthalpyFromDensity - ) == barotrope.density_from_enthalpy(enthalpyFromDensity) + barotrope.pressure_derivative_from_enthalpy(enthalpyFromDensity) == + barotrope.density_from_enthalpy(enthalpyFromDensity) ); /* @@ -149,9 +119,7 @@ TEST_CASE( */ CHECK_THAT( barotrope.pressure_derivative_from_density(density), - Catch::Matchers::WithinRel( - enthalpyFromDensity / polytropicIndex, 5.0e-13 - ) + Catch::Matchers::WithinRel(enthalpyFromDensity / polytropicIndex, 5.0e-13) ); } } @@ -169,63 +137,41 @@ TEST_CASE( constexpr double polytropicConstant = 0.61; for (const double polytropicIndex : polytropicIndices) { - const mean_field::physics::PolytropicBarotrope barotrope( - polytropicIndex, polytropicConstant - ); + const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { for (const double enthalpy : positiveValues) { - const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); + const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); - const double numericalDerivative = - polytropic_barotrope_test_utils::centered_derivative( - [&barotrope](const double perturbedEnthalpy) { - return barotrope.pressure_from_enthalpy( - perturbedEnthalpy - ); - }, - enthalpy, step - ); - - const double analyticDerivative = - barotrope.pressure_derivative_from_enthalpy(enthalpy); - - CAPTURE( - polytropicIndex, enthalpy, step, numericalDerivative, - analyticDerivative + const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( + [&barotrope](const double perturbedEnthalpy) { + return barotrope.pressure_from_enthalpy(perturbedEnthalpy); + }, + enthalpy, step ); - CHECK_THAT( - numericalDerivative, - Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8) - ); + const double analyticDerivative = barotrope.pressure_derivative_from_enthalpy(enthalpy); + + CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative); + + CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); } for (const double density : positiveValues) { - const double step = 2.0e-6 * std::max(1.0, std::abs(density)); + const double step = 2.0e-6 * std::max(1.0, std::abs(density)); - const double numericalDerivative = - polytropic_barotrope_test_utils::centered_derivative( - [&barotrope](const double perturbedDensity) { - return barotrope.pressure_from_density( - perturbedDensity - ); - }, - density, step - ); - - const double analyticDerivative = - barotrope.pressure_derivative_from_density(density); - - CAPTURE( - polytropicIndex, density, step, numericalDerivative, - analyticDerivative + const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( + [&barotrope](const double perturbedDensity) { + return barotrope.pressure_from_density(perturbedDensity); + }, + density, step ); - CHECK_THAT( - numericalDerivative, - Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8) - ); + const double analyticDerivative = barotrope.pressure_derivative_from_density(density); + + CAPTURE(polytropicIndex, density, step, numericalDerivative, analyticDerivative); + + CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); } } } @@ -242,36 +188,24 @@ TEST_CASE( constexpr double polytropicConstant = 0.61; for (const double polytropicIndex : polytropicIndices) { - const mean_field::physics::PolytropicBarotrope barotrope( - polytropicIndex, polytropicConstant - ); + const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { for (const double enthalpy : enthalpies) { - const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); + const double step = 2.0e-6 * std::max(1.0, std::abs(enthalpy)); - const double numericalDerivative = - polytropic_barotrope_test_utils::centered_derivative( - [&barotrope](const double perturbedEnthalpy) { - return barotrope.density_from_enthalpy( - perturbedEnthalpy - ); - }, - enthalpy, step - ); - - const double analyticDerivative = - barotrope.density_derivative_from_enthalpy(enthalpy); - - CAPTURE( - polytropicIndex, enthalpy, step, numericalDerivative, - analyticDerivative + const double numericalDerivative = polytropic_barotrope_test_utils::centered_derivative( + [&barotrope](const double perturbedEnthalpy) { + return barotrope.density_from_enthalpy(perturbedEnthalpy); + }, + enthalpy, step ); - CHECK_THAT( - numericalDerivative, - Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8) - ); + const double analyticDerivative = barotrope.density_derivative_from_enthalpy(enthalpy); + + CAPTURE(polytropicIndex, enthalpy, step, numericalDerivative, analyticDerivative); + + CHECK_THAT(numericalDerivative, Catch::Matchers::WithinRel(analyticDerivative, 5.0e-8)); } } } @@ -287,9 +221,7 @@ TEST_CASE( constexpr double exteriorEnthalpy = -0.3; for (const double polytropicIndex : polytropicIndices) { - const mean_field::physics::PolytropicBarotrope barotrope( - polytropicIndex, polytropicConstant - ); + const mean_field::physics::PolytropicBarotrope barotrope(polytropicIndex, polytropicConstant); DYNAMIC_SECTION("polytropic index n = " << polytropicIndex) { /* @@ -314,15 +246,9 @@ TEST_CASE( CHECK(barotrope.pressure_from_enthalpy(exteriorEnthalpy) == 0.0); - CHECK( - barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) == - 0.0 - ); + CHECK(barotrope.density_derivative_from_enthalpy(exteriorEnthalpy) == 0.0); - CHECK( - barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) == - 0.0 - ); + CHECK(barotrope.pressure_derivative_from_enthalpy(exteriorEnthalpy) == 0.0); /* * At h = 0, rho(h) has a nonzero right @@ -331,10 +257,7 @@ TEST_CASE( const double expectedSurfaceDensityDerivative = polytropicIndex == 1.0 ? 1.0 / barotrope.enthalpy_scale() : 0.0; - CHECK( - barotrope.density_derivative_from_enthalpy(0.0) == - expectedSurfaceDensityDerivative - ); + CHECK(barotrope.density_derivative_from_enthalpy(0.0) == expectedSurfaceDensityDerivative); } } } @@ -343,27 +266,15 @@ TEST_CASE( "Polytropic Barotrope Rejects Invalid Physical Inputs", tags::barotrope &tags::physics &tags::unit &tags::pressure ) { - CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope(0.999, 1.0), - std::invalid_argument - ); + CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(0.999, 1.0), std::invalid_argument); CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope( - std::numeric_limits::infinity(), 1.0 - ), - std::invalid_argument + mean_field::physics::PolytropicBarotrope(std::numeric_limits::infinity(), 1.0), std::invalid_argument ); - CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope(3.0, 0.0), - std::invalid_argument - ); + CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, 0.0), std::invalid_argument); - CHECK_THROWS_AS( - mean_field::physics::PolytropicBarotrope(3.0, -1.0), - std::invalid_argument - ); + CHECK_THROWS_AS(mean_field::physics::PolytropicBarotrope(3.0, -1.0), std::invalid_argument); const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.75); @@ -371,45 +282,31 @@ TEST_CASE( CHECK_THROWS_AS(barotrope.enthalpy_from_density(-0.1), std::domain_error); - CHECK_THROWS_AS( - barotrope.pressure_derivative_from_density(-0.1), std::domain_error - ); + CHECK_THROWS_AS(barotrope.pressure_derivative_from_density(-0.1), std::domain_error); constexpr std::array nonfiniteValues{ - std::numeric_limits::infinity(), - -std::numeric_limits::infinity(), + std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN() }; for (const double nonfiniteValue : nonfiniteValues) { CAPTURE(nonfiniteValue); - CHECK_THROWS_AS( - barotrope.density_from_enthalpy(nonfiniteValue), std::domain_error - ); + CHECK_THROWS_AS(barotrope.density_from_enthalpy(nonfiniteValue), std::domain_error); - CHECK_THROWS_AS( - barotrope.pressure_from_enthalpy(nonfiniteValue), std::domain_error - ); + CHECK_THROWS_AS(barotrope.pressure_from_enthalpy(nonfiniteValue), std::domain_error); - CHECK_THROWS_AS( - barotrope.density_derivative_from_enthalpy(nonfiniteValue), - std::domain_error - ); + CHECK_THROWS_AS(barotrope.density_derivative_from_enthalpy(nonfiniteValue), std::domain_error); - CHECK_THROWS_AS( - barotrope.pressure_derivative_from_enthalpy(nonfiniteValue), - std::domain_error - ); + CHECK_THROWS_AS(barotrope.pressure_derivative_from_enthalpy(nonfiniteValue), std::domain_error); } } TEST_CASE( "Pressure Force And Pressure Integral Have Distinct Registered Forms", - tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature - &tags::unit + tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::unit ) { - using EnthalpyField = mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; /* * For the registered H1 order p = 3 and n = 3: @@ -423,39 +320,29 @@ TEST_CASE( * * beyond the registered enthalpy operand. */ - constexpr int enthalpyOrder = - mean_field::field::Enthalpy::Scalar::familyOrder; + constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder; constexpr int pressureExtraOrder = 3 * enthalpyOrder; constexpr int geometryWeightOrder = 2; constexpr mean_field::quadrature::Query pressureIntegralQuery = - EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::PressureIntegral>( - mean_field::quadrature::QuadratureRole::diagnostic, - geometryWeightOrder, std::array{pressureExtraOrder}, - mean_field::utils::DOMAINS::STELLAR, + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::diagnostic, geometryWeightOrder, + std::array{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); constexpr mean_field::quadrature::Query pressureForceQuery = - EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::PressureForce>( - mean_field::quadrature::QuadratureRole::discretization, - geometryWeightOrder, std::array{pressureExtraOrder}, - mean_field::utils::DOMAINS::STELLAR, + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, geometryWeightOrder, + std::array{pressureExtraOrder}, mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::general ); - STATIC_CHECK( - mean_field::field::Enthalpy::Form::PressureIntegral:: - dynamicOrderCount == 1 - ); + STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureIntegral::dynamicOrderCount == 1); - STATIC_CHECK( - mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1 - ); + STATIC_CHECK(mean_field::field::Enthalpy::Form::PressureForce::dynamicOrderCount == 1); STATIC_CHECK( mean_field::field::Enthalpy::Form::PressureIntegral::policyKey != @@ -487,24 +374,13 @@ TEST_CASE( */ CHECK(*pressureForceQuery.base_order == 16); - CHECK( - pressureIntegralQuery.term == - mean_field::quadrature::Term::pressure_integral - ); + CHECK(pressureIntegralQuery.term == mean_field::quadrature::Term::pressure_integral); - CHECK( - pressureForceQuery.term == mean_field::quadrature::Term::pressure_force - ); + CHECK(pressureForceQuery.term == mean_field::quadrature::Term::pressure_force); - CHECK( - pressureIntegralQuery.role == - mean_field::quadrature::QuadratureRole::diagnostic - ); + CHECK(pressureIntegralQuery.role == mean_field::quadrature::QuadratureRole::diagnostic); - CHECK( - pressureForceQuery.role == - mean_field::quadrature::QuadratureRole::discretization - ); + CHECK(pressureForceQuery.role == mean_field::quadrature::QuadratureRole::discretization); CHECK(pressureIntegralQuery.domain == mean_field::utils::DOMAINS::STELLAR); @@ -515,20 +391,16 @@ TEST_CASE( * controls. */ mean_field::quadrature::RuleSet ruleSet = - mean_field::quadrature::make_rule_set( - mean_field::quadrature::Mode::production - ); + mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production); ruleSet.pressure_integral.boost = 3; ruleSet.pressure_force.boost = 5; const mean_field::quadrature::Policy policy(std::move(ruleSet)); - const mean_field::quadrature::Resolution pressureIntegralResolution = - policy.resolve(pressureIntegralQuery); + const mean_field::quadrature::Resolution pressureIntegralResolution = policy.resolve(pressureIntegralQuery); - const mean_field::quadrature::Resolution pressureForceResolution = - policy.resolve(pressureForceQuery); + const mean_field::quadrature::Resolution pressureForceResolution = policy.resolve(pressureForceQuery); CHECK(pressureIntegralResolution.base_order == 14); @@ -544,14 +416,12 @@ TEST_CASE( } TEST_CASE( - "Stage Four Pressure Quadrature Exactly Integrates An N Three Polynomial", - tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature - &tags::accuracy + "Pressure Quadrature Exactly Integrates An N Three Polynomial", + tags::barotrope &tags::pressure &tags::pressure_gradient &tags::quadrature &tags::accuracy ) { - using EnthalpyField = mean_field::field::Field; + using EnthalpyField = mean_field::field::Field; - constexpr int enthalpyOrder = - mean_field::field::Enthalpy::Scalar::familyOrder; + constexpr int enthalpyOrder = mean_field::field::Enthalpy::Scalar::familyOrder; constexpr int pressureExtraOrder = 3 * enthalpyOrder; @@ -565,29 +435,19 @@ TEST_CASE( const mean_field::physics::PolytropicBarotrope barotrope(3.0, 0.25); constexpr mean_field::quadrature::Query pressureIntegralQuery = - EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::PressureIntegral>( - mean_field::quadrature::QuadratureRole::diagnostic, 0, - std::array{pressureExtraOrder}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::affine + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::diagnostic, 0, std::array{pressureExtraOrder}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine ); constexpr mean_field::quadrature::Query pressureForceQuery = - EnthalpyField::make_query< - mean_field::field::Enthalpy::Form::PressureForce>( - mean_field::quadrature::QuadratureRole::discretization, 0, - std::array{pressureExtraOrder}, - mean_field::utils::DOMAINS::STELLAR, - mean_field::quadrature::MappingKind::affine + EnthalpyField::make_query( + mean_field::quadrature::QuadratureRole::discretization, 0, std::array{pressureExtraOrder}, + mean_field::utils::DOMAINS::STELLAR, mean_field::quadrature::MappingKind::affine ); const mean_field::quadrature::RuleFactory ruleFactory{ - mean_field::quadrature::Policy( - mean_field::quadrature::make_rule_set( - mean_field::quadrature::Mode::production - ) - ) + mean_field::quadrature::Policy(mean_field::quadrature::make_rule_set(mean_field::quadrature::Mode::production)) }; const mean_field::quadrature::MfemRule pressureIntegralRule = @@ -606,21 +466,17 @@ TEST_CASE( * * P = x^12 y^12 z^12 / 4. */ - const double numericalPressureIntegral = - polytropic_barotrope_test_utils::integrate_cube( - *pressureIntegralRule.integration_rule, - [&barotrope](const mfem::IntegrationPoint &integrationPoint) { - const double coordinateProduct = integrationPoint.x * - integrationPoint.y * - integrationPoint.z; + const double numericalPressureIntegral = polytropic_barotrope_test_utils::integrate_cube( + *pressureIntegralRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) { + const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z; - const double enthalpy = std::pow(coordinateProduct, 3.0); + const double enthalpy = std::pow(coordinateProduct, 3.0); - return barotrope.pressure_from_enthalpy(enthalpy); - } - ); + return barotrope.pressure_from_enthalpy(enthalpy); + } + ); - const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0); + const double analyticPressureIntegral = 0.25 / std::pow(13.0, 3.0); /* * Choose a representable vector test function whose @@ -633,51 +489,34 @@ TEST_CASE( * -P div(w) * = -x^14 y^14 z^14 / 4. */ - const double numericalPressureForceIntegral = - polytropic_barotrope_test_utils::integrate_cube( - *pressureForceRule.integration_rule, - [&barotrope](const mfem::IntegrationPoint &integrationPoint) { - const double coordinateProduct = integrationPoint.x * - integrationPoint.y * - integrationPoint.z; + const double numericalPressureForceIntegral = polytropic_barotrope_test_utils::integrate_cube( + *pressureForceRule.integration_rule, [&barotrope](const mfem::IntegrationPoint &integrationPoint) { + const double coordinateProduct = integrationPoint.x * integrationPoint.y * integrationPoint.z; - const double enthalpy = std::pow(coordinateProduct, 3.0); + const double enthalpy = std::pow(coordinateProduct, 3.0); - const double pressure = - barotrope.pressure_from_enthalpy(enthalpy); + const double pressure = barotrope.pressure_from_enthalpy(enthalpy); - const double testDivergence = - integrationPoint.x * integrationPoint.x * - integrationPoint.y * integrationPoint.y * - integrationPoint.z * integrationPoint.z; + const double testDivergence = integrationPoint.x * integrationPoint.x * integrationPoint.y * + integrationPoint.y * integrationPoint.z * integrationPoint.z; - return -pressure * testDivergence; - } - ); + return -pressure * testDivergence; + } + ); const double analyticPressureForceIntegral = -0.25 / std::pow(15.0, 3.0); - INFO( - "Pressure-integral quadrature order = " - << pressureIntegralRule.resolution.order - ); + INFO("Pressure-integral quadrature order = " << pressureIntegralRule.resolution.order); - INFO( - "Pressure-force quadrature order = " - << pressureForceRule.resolution.order - ); + INFO("Pressure-force quadrature order = " << pressureForceRule.resolution.order); INFO("Numerical pressure integral = " << numericalPressureIntegral); INFO("Analytic pressure integral = " << analyticPressureIntegral); - INFO( - "Numerical pressure-force integral = " << numericalPressureForceIntegral - ); + INFO("Numerical pressure-force integral = " << numericalPressureForceIntegral); - INFO( - "Analytic pressure-force integral = " << analyticPressureForceIntegral - ); + INFO("Analytic pressure-force integral = " << analyticPressureForceIntegral); CHECK(pressureIntegralRule.resolution.base_order == 12); @@ -687,13 +526,7 @@ TEST_CASE( CHECK(pressureForceRule.resolution.order == 14); - CHECK_THAT( - numericalPressureIntegral, - Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14) - ); + CHECK_THAT(numericalPressureIntegral, Catch::Matchers::WithinAbs(analyticPressureIntegral, 5.0e-14)); - CHECK_THAT( - numericalPressureForceIntegral, - Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14) - ); + CHECK_THAT(numericalPressureForceIntegral, Catch::Matchers::WithinAbs(analyticPressureForceIntegral, 5.0e-14)); } \ No newline at end of file diff --git a/tests/physics/gravity.cpp b/tests/physics/gravity.cpp index 60d736e..083b46a 100644 --- a/tests/physics/gravity.cpp +++ b/tests/physics/gravity.cpp @@ -24,10 +24,7 @@ namespace { ) { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -38,9 +35,7 @@ namespace { ) { const double local_dot = lhs * rhs; double global_dot = 0.0; - MPI_Allreduce( - &local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator); return global_dot; } @@ -52,10 +47,7 @@ namespace { mfem::Vector difference(computed); difference -= reference; return global_vector_norm(difference, communicator) / - std::max( - global_vector_norm(reference, communicator), - std::numeric_limits::epsilon() - ); + std::max(global_vector_norm(reference, communicator), std::numeric_limits::epsilon()); } struct GravitationalEnergies { @@ -97,23 +89,15 @@ namespace { mfem::DenseMatrix map_jacobian(3, 3); mfem::DenseMatrix inverse_map_jacobian(3, 3); - domain_mapping.GetPhysicalPoint( - transformation, integration_point, x_physical - ); + domain_mapping.GetPhysicalPoint(transformation, integration_point, x_physical); - field_physical(0) = - 2.0 * M_PI * utils::G * density * coefficient_x * x_physical(0); - field_physical(1) = - 2.0 * M_PI * utils::G * density * coefficient_y * x_physical(1); - field_physical(2) = - 2.0 * M_PI * utils::G * density * coefficient_z * x_physical(2); + field_physical(0) = 2.0 * M_PI * utils::G * density * coefficient_x * x_physical(0); + field_physical(1) = 2.0 * M_PI * utils::G * density * coefficient_y * x_physical(1); + field_physical(2) = 2.0 * M_PI * utils::G * density * coefficient_z * x_physical(2); domain_mapping.ComputeJacobian(transformation, map_jacobian); const double map_determinant = map_jacobian.Det(); - MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian determinant." - ); + MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); mfem::CalcInverse(map_jacobian, inverse_map_jacobian); inverse_map_jacobian.Mult(field_physical, value); @@ -149,61 +133,44 @@ namespace { continue; } - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - double weight = - transformation->Weight() * integration_point.weight; + double weight = transformation->Weight() * integration_point.weight; if (f.has_mapping()) { - const double map_determinant = f.mapping->ComputeDetJ( - *transformation, integration_point - ); + const double map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian " - "determinant." + map_determinant > 0.0, "Domain mapping has a non-positive Jacobian " + "determinant." ); weight *= map_determinant; - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); - gravity_solution.gradPhi.GetVectorValue( - elem_id, integration_point, grad_phi_element - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); + gravity_solution.gradPhi.GetVectorValue(elem_id, integration_point, grad_phi_element); f.mapping->ComputeJacobian(*transformation, map_jacobian); map_jacobian.Mult(grad_phi_element, grad_phi_physical); grad_phi_physical /= map_determinant; } else { transformation->Transform(integration_point, x_physical); - gravity_solution.gradPhi.GetVectorValue( - elem_id, integration_point, grad_phi_physical - ); + gravity_solution.gradPhi.GetVectorValue(elem_id, integration_point, grad_phi_physical); } - const double rho_value = - rho.GetValue(elem_id, integration_point); - const double phi_value = - gravity_solution.phi.GetValue(elem_id, integration_point); + const double rho_value = rho.GetValue(elem_id, integration_point); + const double phi_value = gravity_solution.phi.GetValue(elem_id, integration_point); double radius_dot_gradient = 0.0; for (int d = 0; d < dim; ++d) { - radius_dot_gradient += - (x_physical(d) - f.com(d)) * grad_phi_physical(d); + radius_dot_gradient += (x_physical(d) - f.com(d)) * grad_phi_physical(d); } local_bind_integral += rho_value * phi_value * weight; - local_virial_integral += - rho_value * radius_dot_gradient * weight; + local_virial_integral += rho_value * radius_dot_gradient * weight; } } @@ -213,12 +180,8 @@ namespace { double global_w_vir = 0.0; MPI_Comm communicator = f.densityFes->GetComm(); - MPI_Allreduce( - &local_w_bind, &global_w_bind, 1, MPI_DOUBLE, MPI_SUM, communicator - ); - MPI_Allreduce( - &local_w_vir, &global_w_vir, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_w_bind, &global_w_bind, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_w_vir, &global_w_vir, 1, MPI_DOUBLE, MPI_SUM, communicator); return {.binding = global_w_bind, .virial = global_w_vir}; } @@ -233,11 +196,7 @@ namespace { } int get_gravity_quadrature_order(const fem::FEM &f) { - return 2 * std::max( - f.gravityPotentialFes->GetMaxElementOrder(), - f.gravityFluxFes->GetMaxElementOrder() - ) + - 8; + return 2 * std::max(f.gravityPotentialFes->GetMaxElementOrder(), f.gravityFluxFes->GetMaxElementOrder()) + 8; } double compute_ellipsoid_coefficient( @@ -261,8 +220,8 @@ namespace { (normalized_axis_z * normalized_axis_z + s_squared) ); - return normalized_axis_x * normalized_axis_y * normalized_axis_z * - ds_squared_dt / ((target_axis_squared + s_squared) * delta); + return normalized_axis_x * normalized_axis_y * normalized_axis_z * ds_squared_dt / + ((target_axis_squared + s_squared) * delta); }; double integration_error = 0.0; @@ -298,11 +257,10 @@ namespace { return ds_squared_dt / delta; }; - double integration_error = 0.0; - const double dimensionless_integral = - boost::math::quadrature::gauss_kronrod::integrate( - integrand, 0.0, 1.0, 15, 1.0e-13, &integration_error - ); + double integration_error = 0.0; + const double dimensionless_integral = boost::math::quadrature::gauss_kronrod::integrate( + integrand, 0.0, 1.0, 15, 1.0e-13, &integration_error + ); return dimensionless_integral / length_scale; } @@ -312,27 +270,21 @@ namespace { const double semi_axis_y, const double semi_axis_z ) { - const double length_scale = - std::cbrt(semi_axis_x * semi_axis_y * semi_axis_z); + const double length_scale = std::cbrt(semi_axis_x * semi_axis_y * semi_axis_z); const double normalized_axis_x = semi_axis_x / length_scale; const double normalized_axis_y = semi_axis_y / length_scale; const double normalized_axis_z = semi_axis_z / length_scale; const double coefficient_x = compute_ellipsoid_coefficient( - normalized_axis_x, normalized_axis_y, normalized_axis_z, - normalized_axis_x * normalized_axis_x + normalized_axis_x, normalized_axis_y, normalized_axis_z, normalized_axis_x * normalized_axis_x ); const double coefficient_y = compute_ellipsoid_coefficient( - normalized_axis_x, normalized_axis_y, normalized_axis_z, - normalized_axis_y * normalized_axis_y + normalized_axis_x, normalized_axis_y, normalized_axis_z, normalized_axis_y * normalized_axis_y ); const double coefficient_z = compute_ellipsoid_coefficient( - normalized_axis_x, normalized_axis_y, normalized_axis_z, - normalized_axis_z * normalized_axis_z - ); - const double energy_kernel = compute_ellipsoid_energy_kernel( - normalized_axis_x, normalized_axis_y, normalized_axis_z, - length_scale + normalized_axis_x, normalized_axis_y, normalized_axis_z, normalized_axis_z * normalized_axis_z ); + const double energy_kernel = + compute_ellipsoid_energy_kernel(normalized_axis_x, normalized_axis_y, normalized_axis_z, length_scale); return { .coefficient_x = coefficient_x, @@ -377,46 +329,31 @@ namespace { continue; } - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - double weight = - transformation->Weight() * integration_point.weight; + double weight = transformation->Weight() * integration_point.weight; - left_solution.gradPhi.GetVectorValue( - element_id, integration_point, left_gradient_element - ); - right_solution.gradPhi.GetVectorValue( - element_id, integration_point, right_gradient_element - ); + left_solution.gradPhi.GetVectorValue(element_id, integration_point, left_gradient_element); + right_solution.gradPhi.GetVectorValue(element_id, integration_point, right_gradient_element); if (f.has_mapping()) { - const double map_determinant = f.mapping->ComputeDetJ( - *transformation, integration_point - ); + const double map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian " - "determinant." + map_determinant > 0.0, "Domain mapping has a non-positive Jacobian " + "determinant." ); weight *= map_determinant; f.mapping->ComputeJacobian(*transformation, map_jacobian); - map_jacobian.Mult( - left_gradient_element, left_gradient_physical - ); - map_jacobian.Mult( - right_gradient_element, right_gradient_physical - ); + map_jacobian.Mult(left_gradient_element, left_gradient_physical); + map_jacobian.Mult(right_gradient_element, right_gradient_physical); left_gradient_physical /= map_determinant; right_gradient_physical /= map_determinant; @@ -428,66 +365,40 @@ namespace { gradient_difference = left_gradient_physical; gradient_difference -= right_gradient_physical; - local_gradient_difference_squared += - (gradient_difference * gradient_difference) * weight; - local_left_gradient_norm_squared += - (left_gradient_physical * left_gradient_physical) * weight; - local_right_gradient_norm_squared += - (right_gradient_physical * right_gradient_physical) * - weight; + local_gradient_difference_squared += (gradient_difference * gradient_difference) * weight; + local_left_gradient_norm_squared += (left_gradient_physical * left_gradient_physical) * weight; + local_right_gradient_norm_squared += (right_gradient_physical * right_gradient_physical) * weight; - const double left_potential = - left_solution.phi.GetValue(element_id, integration_point); - const double right_potential = - right_solution.phi.GetValue(element_id, integration_point); - const double potential_difference = - left_potential - right_potential; + const double left_potential = left_solution.phi.GetValue(element_id, integration_point); + const double right_potential = right_solution.phi.GetValue(element_id, integration_point); + const double potential_difference = left_potential - right_potential; - local_potential_difference_squared += - potential_difference * potential_difference * weight; - local_left_potential_norm_squared += - left_potential * left_potential * weight; - local_right_potential_norm_squared += - right_potential * right_potential * weight; + local_potential_difference_squared += potential_difference * potential_difference * weight; + local_left_potential_norm_squared += left_potential * left_potential * weight; + local_right_potential_norm_squared += right_potential * right_potential * weight; } } - const std::array local_values{ - local_gradient_difference_squared, - local_left_gradient_norm_squared, - local_right_gradient_norm_squared, - local_potential_difference_squared, - local_left_potential_norm_squared, - local_right_potential_norm_squared - }; + const std::array local_values{local_gradient_difference_squared, local_left_gradient_norm_squared, + local_right_gradient_norm_squared, local_potential_difference_squared, + local_left_potential_norm_squared, local_right_potential_norm_squared}; std::array global_values{}; MPI_Allreduce( - local_values.data(), global_values.data(), - static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, + local_values.data(), global_values.data(), static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, f.densityFes->GetComm() ); - const double gradient_scale_squared = - 0.5 * (global_values[1] + global_values[2]); - const double potential_scale_squared = - 0.5 * (global_values[4] + global_values[5]); + const double gradient_scale_squared = 0.5 * (global_values[1] + global_values[2]); + const double potential_scale_squared = 0.5 * (global_values[4] + global_values[5]); - MFEM_VERIFY( - gradient_scale_squared > 0.0, - "Cannot compare gravity solutions with zero gradient norm." - ); - MFEM_VERIFY( - potential_scale_squared > 0.0, - "Cannot compare gravity solutions with zero potential norm." - ); + MFEM_VERIFY(gradient_scale_squared > 0.0, "Cannot compare gravity solutions with zero gradient norm."); + MFEM_VERIFY(potential_scale_squared > 0.0, "Cannot compare gravity solutions with zero potential norm."); return { - .relative_gradient_difference = - std::sqrt(global_values[0] / gradient_scale_squared), - .relative_potential_difference = - std::sqrt(global_values[3] / potential_scale_squared) + .relative_gradient_difference = std::sqrt(global_values[0] / gradient_scale_squared), + .relative_potential_difference = std::sqrt(global_values[3] / potential_scale_squared) }; } @@ -503,10 +414,7 @@ namespace { ) { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -515,23 +423,16 @@ namespace { const mfem::GridFunction &grid_function, mfem::Vector &true_dofs ) { - MFEM_VERIFY( - grid_function.Size() == finite_element_space.GetVSize(), - "Grid function has the wrong local size." - ); + MFEM_VERIFY(grid_function.Size() == finite_element_space.GetVSize(), "Grid function has the wrong local size."); true_dofs.SetSize(finite_element_space.GetTrueVSize()); - const mfem::Operator *restriction = - finite_element_space.GetRestrictionMatrix(); + const mfem::Operator *restriction = finite_element_space.GetRestrictionMatrix(); if (restriction != nullptr) { restriction->Mult(grid_function, true_dofs); } else { - MFEM_VERIFY( - grid_function.Size() == true_dofs.Size(), - "Local and true sizes do not match." - ); + MFEM_VERIFY(grid_function.Size() == true_dofs.Size(), "Local and true sizes do not match."); true_dofs = grid_function; } } @@ -542,17 +443,9 @@ namespace { const double right_hand_side_norm, MPI_Comm communicator ) { - MFEM_VERIFY( - offsets.Size() == 3, "Gravity residual must contain two blocks." - ); - MFEM_VERIFY( - residual.Size() == offsets.Last(), - "Gravity residual has the wrong size." - ); - MFEM_VERIFY( - right_hand_side_norm > 0.0, - "Gravity right-hand side must be nonzero." - ); + MFEM_VERIFY(offsets.Size() == 3, "Gravity residual must contain two blocks."); + MFEM_VERIFY(residual.Size() == offsets.Last(), "Gravity residual has the wrong size."); + MFEM_VERIFY(right_hand_side_norm > 0.0, "Gravity right-hand side must be nonzero."); mfem::Vector gradient_residual(offsets[1] - offsets[0]); mfem::Vector poisson_residual(offsets[2] - offsets[1]); @@ -566,14 +459,9 @@ namespace { } return { - .relative_total = gravity_test_global_norm(residual, communicator) / - right_hand_side_norm, - .relative_gradient = - gravity_test_global_norm(gradient_residual, communicator) / - right_hand_side_norm, - .relative_poisson = - gravity_test_global_norm(poisson_residual, communicator) / - right_hand_side_norm + .relative_total = gravity_test_global_norm(residual, communicator) / right_hand_side_norm, + .relative_gradient = gravity_test_global_norm(gradient_residual, communicator) / right_hand_side_norm, + .relative_poisson = gravity_test_global_norm(poisson_residual, communicator) / right_hand_side_norm }; } @@ -598,21 +486,16 @@ namespace { double tangential_field_squared{0.0}; }; - constexpr std::array exterior_shell_boundaries{0.0, 0.25, 0.50, - 0.75, 0.90, 1.0}; + constexpr std::array exterior_shell_boundaries{0.0, 0.25, 0.50, 0.75, 0.90, 1.0}; int get_exterior_shell(const double compactification_coordinate) { REQUIRE(std::isfinite(compactification_coordinate)); REQUIRE(compactification_coordinate >= -1.0e-12); REQUIRE(compactification_coordinate <= 1.0 + 1.0e-12); - const double coordinate = std::clamp( - compactification_coordinate, 0.0, std::nextafter(1.0, 0.0) - ); + const double coordinate = std::clamp(compactification_coordinate, 0.0, std::nextafter(1.0, 0.0)); - for (int shell = 0; - shell < static_cast(exterior_shell_boundaries.size()) - 1; - ++shell) { + for (int shell = 0; shell < static_cast(exterior_shell_boundaries.size()) - 1; ++shell) { if (coordinate < exterior_shell_boundaries[shell + 1]) { return shell; } @@ -639,24 +522,18 @@ namespace { REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr); - constexpr int shell_count = - static_cast(exterior_shell_boundaries.size()) - 1; + constexpr int shell_count = static_cast(exterior_shell_boundaries.size()) - 1; - std::array - local_shells{}; + std::array local_shells{}; - mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); const int quadrature_order = get_gravity_quadrature_order(f); for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); REQUIRE(transformation != nullptr); @@ -664,49 +541,36 @@ namespace { continue; } - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); mfem::Array displacement_dofs; mfem::Array compactification_dofs; mfem::DofTransformation *displacement_dof_transformation = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; displacement.GetSubVector(displacement_dofs, element_displacement); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); } if (compactification_dof_transformation != nullptr) { - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); } const mapping::ElementDisplacementData displacement_data( - displacement_element, element_displacement, - f.displacementFes->GetOrdering() + displacement_element, element_displacement, f.displacementFes->GetOrdering() ); const mapping::ElementCompactificationData compactification_data( @@ -714,62 +578,43 @@ namespace { ); const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - mfem::Vector compactification_shape( - compactification_element.GetDof() - ); + mfem::Vector compactification_shape(compactification_element.GetDof()); - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - compactification_element.CalcShape( - integration_point, compactification_shape - ); + compactification_element.CalcShape(integration_point, compactification_shape); - const double compactification_coordinate = - element_compactification * compactification_shape; + const double compactification_coordinate = element_compactification * compactification_shape; - const int shell = - get_exterior_shell(compactification_coordinate); + const int shell = get_exterior_shell(compactification_coordinate); mfem::Vector reference_field(3); mfem::Vector physical_field(3); mfem::Vector physical_position(3); - solution.gradPhi.GetVectorValue( - element_id, integration_point, reference_field - ); + solution.gradPhi.GetVectorValue(element_id, integration_point, reference_field); if (mapping_path == ExteriorMonopoleMapping::stateless) { mapping::VolumeMappingContext mapping_context; - const mapping::MappingStatus status = - f.domainMapperStateless->EvaluateVolume( - mapping_data, *transformation, integration_point, - workspace, mapping_context - ); - - CAPTURE( - element_id, q, compactification_coordinate, - static_cast(status) + const mapping::MappingStatus status = f.domainMapperStateless->EvaluateVolume( + mapping_data, *transformation, integration_point, workspace, mapping_context ); - REQUIRE( - status == mean_field::mapping::MappingStatus::valid - ); + CAPTURE(element_id, q, compactification_coordinate, static_cast(status)); - physical_position = - mapping_context.mapping.physical_position; + REQUIRE(status == mean_field::mapping::MappingStatus::valid); + + physical_position = mapping_context.mapping.physical_position; mean_field::mapping::MapHDivFluxToPhysical( mapping_context.mapping, reference_field, physical_field @@ -779,14 +624,10 @@ namespace { * The legacy path intentionally does not use the exterior * coordinate to construct its physical mapping. */ - f.mapping->GetPhysicalPoint( - *transformation, integration_point, physical_position - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, physical_position); mfem::DenseMatrix mapping_jacobian(3); - f.mapping->ComputeJacobian( - *transformation, mapping_jacobian - ); + f.mapping->ComputeJacobian(*transformation, mapping_jacobian); const double mapping_determinant = mapping_jacobian.Det(); @@ -800,9 +641,7 @@ namespace { const double radius = physical_position.Norml2(); - CAPTURE( - element_id, q, shell, compactification_coordinate, radius - ); + CAPTURE(element_id, q, shell, compactification_coordinate, radius); REQUIRE(std::isfinite(radius)); REQUIRE(radius > 0.0); @@ -810,16 +649,12 @@ namespace { mfem::Vector radial_unit_vector(physical_position); radial_unit_vector /= radius; - const double numerical_radial_field = - physical_field * radial_unit_vector; + const double numerical_radial_field = physical_field * radial_unit_vector; mfem::Vector tangential_field(physical_field); - tangential_field.Add( - -numerical_radial_field, radial_unit_vector - ); + tangential_field.Add(-numerical_radial_field, radial_unit_vector); - const double numerical_potential = - solution.phi.GetValue(element_id, integration_point); + const double numerical_potential = solution.phi.GetValue(element_id, integration_point); /* * For an exterior monopole: @@ -830,16 +665,11 @@ namespace { * These scaled quantities should therefore be one, one, and * zero respectively. They remain well-conditioned as r -> inf. */ - const double scaled_potential = - -radius * numerical_potential / (utils::G * mass); + const double scaled_potential = -radius * numerical_potential / (utils::G * mass); - const double scaled_radial_field = radius * radius * - numerical_radial_field / - (utils::G * mass); + const double scaled_radial_field = radius * radius * numerical_radial_field / (utils::G * mass); - const double scaled_tangential_field = - radius * radius * tangential_field.Norml2() / - (utils::G * mass); + const double scaled_tangential_field = radius * radius * tangential_field.Norml2() / (utils::G * mass); REQUIRE(std::isfinite(scaled_potential)); REQUIRE(std::isfinite(scaled_radial_field)); @@ -850,31 +680,24 @@ namespace { * physical L2 norm of phi over an infinite three-dimensional * exterior domain is not finite. */ - const double reference_weight = - integration_point.weight * transformation->Weight(); + const double reference_weight = integration_point.weight * transformation->Weight(); - ExteriorMonopoleShellAccumulator &accumulator = - local_shells[shell]; + ExteriorMonopoleShellAccumulator &accumulator = local_shells[shell]; ++accumulator.quadrature_points; - accumulator.minimum_radius = - std::min(accumulator.minimum_radius, radius); + accumulator.minimum_radius = std::min(accumulator.minimum_radius, radius); - accumulator.maximum_radius = - std::max(accumulator.maximum_radius, radius); + accumulator.maximum_radius = std::max(accumulator.maximum_radius, radius); accumulator.reference_weight += reference_weight; - accumulator.potential_error_squared += - reference_weight * std::pow(scaled_potential - 1.0, 2); + accumulator.potential_error_squared += reference_weight * std::pow(scaled_potential - 1.0, 2); - accumulator.radial_field_error_squared += - reference_weight * std::pow(scaled_radial_field - 1.0, 2); + accumulator.radial_field_error_squared += reference_weight * std::pow(scaled_radial_field - 1.0, 2); accumulator.tangential_field_squared += - reference_weight * scaled_tangential_field * - scaled_tangential_field; + reference_weight * scaled_tangential_field * scaled_tangential_field; } } @@ -886,49 +709,39 @@ namespace { long long global_points = 0; MPI_Allreduce( - &local_shells[shell].quadrature_points, &global_points, 1, - MPI_LONG_LONG, MPI_SUM, communicator + &local_shells[shell].quadrature_points, &global_points, 1, MPI_LONG_LONG, MPI_SUM, communicator ); double local_sums[4]{ - local_shells[shell].reference_weight, - local_shells[shell].potential_error_squared, - local_shells[shell].radial_field_error_squared, - local_shells[shell].tangential_field_squared + local_shells[shell].reference_weight, local_shells[shell].potential_error_squared, + local_shells[shell].radial_field_error_squared, local_shells[shell].tangential_field_squared }; double global_sums[4]{}; - MPI_Allreduce( - local_sums, global_sums, 4, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(local_sums, global_sums, 4, MPI_DOUBLE, MPI_SUM, communicator); double global_minimum_radius = 0.0; double global_maximum_radius = 0.0; MPI_Allreduce( - &local_shells[shell].minimum_radius, &global_minimum_radius, 1, - MPI_DOUBLE, MPI_MIN, communicator + &local_shells[shell].minimum_radius, &global_minimum_radius, 1, MPI_DOUBLE, MPI_MIN, communicator ); MPI_Allreduce( - &local_shells[shell].maximum_radius, &global_maximum_radius, 1, - MPI_DOUBLE, MPI_MAX, communicator + &local_shells[shell].maximum_radius, &global_maximum_radius, 1, MPI_DOUBLE, MPI_MAX, communicator ); REQUIRE(global_points > 0); REQUIRE(global_sums[0] > 0.0); metrics[shell] = { - .quadrature_points = global_points, - .minimum_radius = global_minimum_radius, - .maximum_radius = global_maximum_radius, - .potential_rms_error = - std::sqrt(global_sums[1] / global_sums[0]), - .radial_field_rms_error = - std::sqrt(global_sums[2] / global_sums[0]), - .tangential_field_rms = - std::sqrt(global_sums[3] / global_sums[0]) + .quadrature_points = global_points, + .minimum_radius = global_minimum_radius, + .maximum_radius = global_maximum_radius, + .potential_rms_error = std::sqrt(global_sums[1] / global_sums[0]), + .radial_field_rms_error = std::sqrt(global_sums[2] / global_sums[0]), + .tangential_field_rms = std::sqrt(global_sums[3] / global_sums[0]) }; } @@ -958,69 +771,47 @@ namespace { const int element_id = transformation.ElementNo; - MFEM_VERIFY( - element_id >= 0, - "Projection coefficient received an invalid element number." - ); + MFEM_VERIFY(element_id >= 0, "Projection coefficient received an invalid element number."); - const mfem::FiniteElement &displacement_element = - *m_fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *m_fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); mfem::Array displacement_dofs; mfem::Array compactification_dofs; mfem::DofTransformation *displacement_dof_transformation = - m_fem.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + m_fem.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_dof_transformation = - m_fem.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + m_fem.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; - m_displacement.GetSubVector( - displacement_dofs, element_displacement - ); + m_displacement.GetSubVector(displacement_dofs, element_displacement); - m_fem.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + m_fem.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_dof_transformation != nullptr) { - displacement_dof_transformation->InvTransformPrimal( - element_displacement - ); + displacement_dof_transformation->InvTransformPrimal(element_displacement); } if (compactification_dof_transformation != nullptr) { - compactification_dof_transformation->InvTransformPrimal( - element_compactification - ); + compactification_dof_transformation->InvTransformPrimal(element_compactification); } const mapping::ElementDisplacementData displacement_data( - displacement_element, element_displacement, - m_fem.displacementFes->GetOrdering() + displacement_element, element_displacement, m_fem.displacementFes->GetOrdering() ); const mapping::ElementCompactificationData compactification_data( compactification_element, element_compactification ); - mfem::Vector requested_compactification_shape( - compactification_element.GetDof() - ); + mfem::Vector requested_compactification_shape(compactification_element.GetDof()); - compactification_element.CalcShape( - integration_point, requested_compactification_shape - ); + compactification_element.CalcShape(integration_point, requested_compactification_shape); const double requested_compactification_coordinate = element_compactification * requested_compactification_shape; @@ -1029,26 +820,21 @@ namespace { const bool requested_infinity_limit = std::isfinite(requested_compactification_coordinate) && - requested_compactification_coordinate >= - 1.0 - infinity_candidate_tolerance; + requested_compactification_coordinate >= 1.0 - infinity_candidate_tolerance; const mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - mapping::MappingStatus status = m_domain_mapper.EvaluatePoint( - mapping_data, transformation, integration_point, m_workspace, - context - ); + mapping::MappingStatus status = + m_domain_mapper.EvaluatePoint(mapping_data, transformation, integration_point, m_workspace, context); if (status == mapping::MappingStatus::valid) { transformation.SetIntPoint(&integration_point); return status; } - if (!permit_infinity_limit || - !m_domain_mapper.IsCompactifiedElement(transformation)) { + if (!permit_infinity_limit || !m_domain_mapper.IsCompactifiedElement(transformation)) { transformation.SetIntPoint(&integration_point); return status; } @@ -1057,45 +843,36 @@ namespace { status == mapping::MappingStatus::at_compactified_infinity || status == mapping::MappingStatus::outside_reference_domain || status == mapping::MappingStatus::non_finite_result || - (requested_infinity_limit && - status == mapping::MappingStatus::non_positive_determinant); + (requested_infinity_limit && status == mapping::MappingStatus::non_positive_determinant); if (!retryable_boundary_status) { transformation.SetIntPoint(&integration_point); return status; } - const mfem::IntegrationPoint &element_center = - mfem::Geometries.GetCenter(transformation.GetGeometryType()); + const mfem::IntegrationPoint &element_center = mfem::Geometries.GetCenter(transformation.GetGeometryType()); /* * Use the nearest admissible point. Starting extremely close to * the requested point preserves the limiting RT trace, while the * larger fallbacks accommodate the mapper's infinity guard. */ - constexpr std::array inward_fractions{ - 1.0e-12, 1.0e-11, 1.0e-10, 1.0e-9, 1.0e-8, - 1.0e-7, 1.0e-6, 1.0e-5, 1.0e-4 - }; + constexpr std::array inward_fractions{1.0e-12, 1.0e-11, 1.0e-10, 1.0e-9, 1.0e-8, + 1.0e-7, 1.0e-6, 1.0e-5, 1.0e-4}; for (const double inward_fraction : inward_fractions) { mfem::IntegrationPoint inward_point; - inward_point.x = (1.0 - inward_fraction) * integration_point.x + - inward_fraction * element_center.x; + inward_point.x = (1.0 - inward_fraction) * integration_point.x + inward_fraction * element_center.x; - inward_point.y = (1.0 - inward_fraction) * integration_point.y + - inward_fraction * element_center.y; + inward_point.y = (1.0 - inward_fraction) * integration_point.y + inward_fraction * element_center.y; - inward_point.z = (1.0 - inward_fraction) * integration_point.z + - inward_fraction * element_center.z; + inward_point.z = (1.0 - inward_fraction) * integration_point.z + inward_fraction * element_center.z; inward_point.weight = integration_point.weight; - status = m_domain_mapper.EvaluatePoint( - mapping_data, transformation, inward_point, m_workspace, - context - ); + status = + m_domain_mapper.EvaluatePoint(mapping_data, transformation, inward_point, m_workspace, context); if (status == mapping::MappingStatus::valid) { m_last_evaluation_used_infinity_limit = true; @@ -1104,14 +881,10 @@ namespace { } const bool still_retryable = - status == - mapping::MappingStatus::at_compactified_infinity || - status == - mapping::MappingStatus::outside_reference_domain || + status == mapping::MappingStatus::at_compactified_infinity || + status == mapping::MappingStatus::outside_reference_domain || status == mapping::MappingStatus::non_finite_result || - (requested_infinity_limit && - status == - mapping::MappingStatus::non_positive_determinant); + (requested_infinity_limit && status == mapping::MappingStatus::non_positive_determinant); if (!still_retryable) { break; } @@ -1133,8 +906,7 @@ namespace { mapping::DomainMapperStateless::Workspace m_workspace; bool m_last_evaluation_used_infinity_limit{false}; }; - class StatelessMonopolePotentialCoefficient final - : public mfem::Coefficient { + class StatelessMonopolePotentialCoefficient final : public mfem::Coefficient { public: StatelessMonopolePotentialCoefficient( const fem::FEM &f, @@ -1159,18 +931,15 @@ namespace { ) override { mapping::MappingPointContext context; - const mapping::MappingStatus status = m_geometry.Evaluate( - transformation, integration_point, context, true - ); + const mapping::MappingStatus status = m_geometry.Evaluate(transformation, integration_point, context, true); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Stateless monopole-potential projection failed." << "\nMapping status = " << static_cast(status) << "\nElement ID = " << transformation.ElementNo - << "\nElement attribute = " << transformation.Attribute - << "\nIntegration point = <" << integration_point.x << ", " - << integration_point.y << ", " << integration_point.z << ">" + << "\nElement attribute = " << transformation.Attribute << "\nIntegration point = <" + << integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">" ); if (m_geometry.LastEvaluationUsedInfinityLimit()) { @@ -1180,19 +949,15 @@ namespace { const double radius = context.physical_position.Norml2(); MFEM_VERIFY( - std::isfinite(radius) && radius > 0.0, - "Monopole projection encountered an invalid physical radius." + std::isfinite(radius) && radius > 0.0, "Monopole projection encountered an invalid physical radius." ); if (transformation.Attribute == m_vacuum_attribute) { return -utils::G * m_mass / radius; } - return -utils::G * m_mass / - (2.0 * m_stellar_radius * m_stellar_radius * - m_stellar_radius) * - (3.0 * m_stellar_radius * m_stellar_radius - - radius * radius); + return -utils::G * m_mass / (2.0 * m_stellar_radius * m_stellar_radius * m_stellar_radius) * + (3.0 * m_stellar_radius * m_stellar_radius - radius * radius); } private: @@ -1202,8 +967,7 @@ namespace { double m_stellar_radius; }; - class StatelessMonopoleHDivCoefficient final - : public mfem::VectorCoefficient { + class StatelessMonopoleHDivCoefficient final : public mfem::VectorCoefficient { public: StatelessMonopoleHDivCoefficient( const fem::FEM &f, @@ -1230,38 +994,31 @@ namespace { ) override { mapping::MappingPointContext context; - const mapping::MappingStatus status = m_geometry.Evaluate( - transformation, integration_point, context, true - ); + const mapping::MappingStatus status = m_geometry.Evaluate(transformation, integration_point, context, true); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Stateless monopole H(div) projection failed." << "\nMapping status = " << static_cast(status) << "\nElement ID = " << transformation.ElementNo - << "\nElement attribute = " << transformation.Attribute - << "\nIntegration point = <" << integration_point.x << ", " - << integration_point.y << ", " << integration_point.z << ">" - << "\nInfinity-limit evaluation attempted = " - << m_geometry.LastEvaluationUsedInfinityLimit() + << "\nElement attribute = " << transformation.Attribute << "\nIntegration point = <" + << integration_point.x << ", " << integration_point.y << ", " << integration_point.z << ">" + << "\nInfinity-limit evaluation attempted = " << m_geometry.LastEvaluationUsedInfinityLimit() ); const mfem::Vector &displaced_position = context.displaced_position; - const double computational_radius = displaced_position.Norml2(); + const double computational_radius = displaced_position.Norml2(); MFEM_VERIFY( - std::isfinite(computational_radius) && - computational_radius > 0.0, + std::isfinite(computational_radius) && computational_radius > 0.0, "Monopole H(div) projection encountered an invalid displaced " "computational radius." ); - const double displacement_determinant = - context.displacement_jacobian.Det(); + const double displacement_determinant = context.displacement_jacobian.Det(); MFEM_VERIFY( - std::isfinite(displacement_determinant) && - displacement_determinant > 0.0, + std::isfinite(displacement_determinant) && displacement_determinant > 0.0, "Monopole H(div) projection encountered an invalid " "displacement " "Jacobian determinant." @@ -1270,13 +1027,10 @@ namespace { mfem::DenseMatrix inverse_displacement_jacobian; inverse_displacement_jacobian.SetSize( - context.displacement_jacobian.Height(), - context.displacement_jacobian.Width() + context.displacement_jacobian.Height(), context.displacement_jacobian.Width() ); - mfem::CalcInverse( - context.displacement_jacobian, inverse_displacement_jacobian - ); + mfem::CalcInverse(context.displacement_jacobian, inverse_displacement_jacobian); /* * Pull the radial field back only through the regular displacement @@ -1297,22 +1051,17 @@ namespace { double radial_denominator = 0.0; if (transformation.Attribute == m_vacuum_attribute) { - radial_denominator = computational_radius * - computational_radius * - computational_radius; + radial_denominator = computational_radius * computational_radius * computational_radius; } else { - radial_denominator = - m_stellar_radius * m_stellar_radius * m_stellar_radius; + radial_denominator = m_stellar_radius * m_stellar_radius * m_stellar_radius; } - value *= mean_field::utils::G * m_mass * displacement_determinant / - radial_denominator; + value *= mean_field::utils::G * m_mass * displacement_determinant / radial_denominator; for (int component = 0; component < value.Size(); ++component) { MFEM_VERIFY( - std::isfinite(value(component)), - "Monopole H(div) projection produced a non-finite " - "reference flux." + std::isfinite(value(component)), "Monopole H(div) projection produced a non-finite " + "reference flux." ); } } @@ -1345,78 +1094,55 @@ TEST_CASE( zero_vacuum_density(f, rho_uniform); analysis::conserve_mass(f, rho_uniform, mass); - f.com = analysis::get_com(f, rho_uniform); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); + f.com = analysis::get_com(f, rho_uniform); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); - const auto gravity_solution = physics::grav_potential(f, args, rho_uniform); - constexpr double potential_tolerance = - utils::APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING; - double local_max_abs_error = 0.0; - double local_max_rel_error = 0.0; + const auto gravity_solution = physics::grav_potential(f, args, rho_uniform); + constexpr double potential_tolerance = utils::APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING; + double local_max_abs_error = 0.0; + double local_max_rel_error = 0.0; - const int num_elements_to_test = std::min(30, f.mesh->GetNE()); + const int num_elements_to_test = std::min(30, f.mesh->GetNE()); for (int elem_id = 0; elem_id < num_elements_to_test; ++elem_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 2); - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(0); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 2); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(0); transformation->SetIntPoint(&integration_point); mfem::Vector x_physical; - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); const double radial_coordinate = x_physical.Norml2(); if (radial_coordinate < 1.0e-9) { continue; } - const double phi_analytic = - -(utils::G * mass / (2.0 * std::pow(radius, 3.0))) * - (3.0 * radius * radius - radial_coordinate * radial_coordinate); - const double phi_fem = - gravity_solution.phi.GetValue(elem_id, integration_point); + const double phi_analytic = -(utils::G * mass / (2.0 * std::pow(radius, 3.0))) * + (3.0 * radius * radius - radial_coordinate * radial_coordinate); + const double phi_fem = gravity_solution.phi.GetValue(elem_id, integration_point); const double absolute_error = std::abs(phi_fem - phi_analytic); const double relative_error = absolute_error / std::abs(phi_analytic); - local_max_abs_error = std::max(local_max_abs_error, absolute_error); - local_max_rel_error = std::max(local_max_rel_error, relative_error); - CHECK_THAT( - relative_error, - Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance) - ); + local_max_abs_error = std::max(local_max_abs_error, absolute_error); + local_max_rel_error = std::max(local_max_rel_error, relative_error); + CHECK_THAT(relative_error, Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance)); } double global_max_abs_error = 0.0; double global_max_rel_error = 0.0; MPI_Comm communicator = f.densityFes->GetComm(); - MPI_Allreduce( - &local_max_abs_error, &global_max_abs_error, 1, MPI_DOUBLE, MPI_MAX, - communicator - ); - MPI_Allreduce( - &local_max_rel_error, &global_max_rel_error, 1, MPI_DOUBLE, MPI_MAX, - communicator - ); + MPI_Allreduce(&local_max_abs_error, &global_max_abs_error, 1, MPI_DOUBLE, MPI_MAX, communicator); + MPI_Allreduce(&local_max_rel_error, &global_max_rel_error, 1, MPI_DOUBLE, MPI_MAX, communicator); - const int quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_uniform, gravity_solution, quadrature_order - ); - const double analytic_binding_energy = - -(3.0 / 5.0) * utils::G * mass * mass / radius; + const int quadrature_order = get_gravity_quadrature_order(f); + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_uniform, gravity_solution, quadrature_order); + const double analytic_binding_energy = -(3.0 / 5.0) * utils::G * mass * mass / radius; const double relative_binding_error = - std::abs(energies.binding - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(energies.binding - analytic_binding_energy) / std::abs(analytic_binding_energy); const double relative_virial_error = - std::abs(energies.virial - analytic_binding_energy) / - std::abs(analytic_binding_energy); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + std::abs(energies.virial - analytic_binding_energy) / std::abs(analytic_binding_energy); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); INFO("Analytic binding energy = " << analytic_binding_energy); INFO("Computed binding energy = " << energies.binding); @@ -1426,25 +1152,11 @@ TEST_CASE( constexpr double energy_tolerance = 1.0e-5; constexpr double consistency_tolerance = 1.0e-6; - CHECK_THAT( - global_max_rel_error, - Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance) - ); - CHECK_THAT( - global_max_abs_error, - Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance) - ); - CHECK_THAT( - relative_binding_error, - Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_virial_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, consistency_tolerance) - ); + CHECK_THAT(global_max_rel_error, Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance)); + CHECK_THAT(global_max_abs_error, Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance)); + CHECK_THAT(relative_binding_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_virial_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, consistency_tolerance)); } TEST_CASE( @@ -1456,23 +1168,18 @@ TEST_CASE( f.mapping->ResetDisplacement(); physics::update_stiffness_matrix(f); - const double radius = utils::RADIUS; - const double mass = utils::MASS; - const double central_density = - (15.0 * mass) / (8.0 * M_PI * std::pow(radius, 3.0)); + const double radius = utils::RADIUS; + const double mass = utils::MASS; + const double central_density = (15.0 * mass) / (8.0 * M_PI * std::pow(radius, 3.0)); - auto parabolic_rho = [central_density, radius](const mfem::Vector &x) { + auto parabolic_rho = [central_density, radius](const mfem::Vector &x) { const double radial_coordinate = x.Norml2(); - return central_density * (1.0 - radial_coordinate * radial_coordinate / - (radius * radius)); + return central_density * (1.0 - radial_coordinate * radial_coordinate / (radius * radius)); }; std::unique_ptr rho_coeff; if (f.has_mapping()) { - rho_coeff = - std::make_unique( - *f.mapping, parabolic_rho - ); + rho_coeff = std::make_unique(*f.mapping, parabolic_rho); } else { rho_coeff = std::make_unique(parabolic_rho); } @@ -1482,25 +1189,19 @@ TEST_CASE( zero_vacuum_density(f, rho_grid); analysis::conserve_mass(f, rho_grid, mass); - f.com = analysis::get_com(f, rho_grid); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); + f.com = analysis::get_com(f, rho_grid); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); const auto gravity_solution = physics::grav_potential(f, args, rho_grid); const int quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_grid, gravity_solution, quadrature_order - ); - const double analytic_binding_energy = - -(5.0 / 7.0) * utils::G * mass * mass / radius; + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_grid, gravity_solution, quadrature_order); + const double analytic_binding_energy = -(5.0 / 7.0) * utils::G * mass * mass / radius; const double relative_binding_error = - std::abs(energies.binding - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(energies.binding - analytic_binding_energy) / std::abs(analytic_binding_energy); const double relative_virial_error = - std::abs(energies.virial - analytic_binding_energy) / - std::abs(analytic_binding_energy); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + std::abs(energies.virial - analytic_binding_energy) / std::abs(analytic_binding_energy); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); INFO("Analytic binding energy = " << analytic_binding_energy); INFO("Computed binding energy = " << energies.binding); @@ -1510,18 +1211,9 @@ TEST_CASE( constexpr double analytic_tolerance = 1.0e-5; constexpr double consistency_tolerance = 1.0e-6; - CHECK_THAT( - relative_binding_error, - Catch::Matchers::WithinAbs(0.0, analytic_tolerance) - ); - CHECK_THAT( - relative_virial_error, - Catch::Matchers::WithinAbs(0.0, analytic_tolerance) - ); - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, consistency_tolerance) - ); + CHECK_THAT(relative_binding_error, Catch::Matchers::WithinAbs(0.0, analytic_tolerance)); + CHECK_THAT(relative_virial_error, Catch::Matchers::WithinAbs(0.0, analytic_tolerance)); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, consistency_tolerance)); } TEST_CASE( @@ -1542,24 +1234,19 @@ TEST_CASE( constexpr double concentration = 16.0; const double density_scale = mass / std::pow(radius, 3.0); - auto rational_rho = [radius, density_scale](const mfem::Vector &x) { + auto rational_rho = [radius, density_scale](const mfem::Vector &x) { const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } - const double denominator = - 1.0 + concentration * normalized_radius_squared; - return density_scale * (1.0 - normalized_radius_squared) / - (denominator * denominator); + const double denominator = 1.0 + concentration * normalized_radius_squared; + return density_scale * (1.0 - normalized_radius_squared) / (denominator * denominator); }; std::unique_ptr rho_coeff; if (f.has_mapping()) { - rho_coeff = - std::make_unique( - *f.mapping, rational_rho - ); + rho_coeff = std::make_unique(*f.mapping, rational_rho); } else { rho_coeff = std::make_unique(rational_rho); } @@ -1569,30 +1256,24 @@ TEST_CASE( zero_vacuum_density(f, rho_grid); analysis::conserve_mass(f, rho_grid, mass); - f.com = analysis::get_com(f, rho_grid); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); + f.com = analysis::get_com(f, rho_grid); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); const auto gravity_solution = physics::grav_potential(f, args, rho_grid); const int quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_grid, gravity_solution, quadrature_order - ); + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_grid, gravity_solution, quadrature_order); REQUIRE(energies.binding < 0.0); REQUIRE(energies.virial < 0.0); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); INFO("W_bind = " << energies.binding); INFO("W_vir = " << energies.virial); INFO("Relative virial consistency error = " << relative_consistency_error); constexpr double virial_tolerance = 1.0e-5; - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, virial_tolerance) - ); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, virial_tolerance)); } TEST_CASE( @@ -1613,8 +1294,7 @@ TEST_CASE( const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - auto affine_displacement = [](const mfem::Vector &x, - mfem::Vector &displacement_value) { + auto affine_displacement = [](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(3); displacement_value(0) = (x_scale - 1.0) * x(0); displacement_value(1) = (y_scale - 1.0) * x(1); @@ -1627,17 +1307,14 @@ TEST_CASE( f.mapping->SetDisplacement(displacement); physics::update_stiffness_matrix(f); - const double analytic_volume = - (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; - const double density = mass / analytic_volume; + const double analytic_volume = (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; + const double density = mass / analytic_volume; mfem::GridFunction rho_grid(f.densityFes.get()); rho_grid = density; zero_vacuum_density(f, rho_grid); - const double projected_mass = analysis::domain_integrate_grid_function( - f, rho_grid, utils::DOMAINS::STELLAR - ); + const double projected_mass = analysis::domain_integrate_grid_function(f, rho_grid, utils::DOMAINS::STELLAR); const double numerical_density = density * mass / projected_mass; analysis::conserve_mass(f, rho_grid, mass); @@ -1645,12 +1322,8 @@ TEST_CASE( f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); const HomogeneousEllipsoidAnalytic analytic = - compute_homogeneous_ellipsoid_analytic( - semi_axis_x, semi_axis_y, semi_axis_z - ); - const double coefficient_sum = analytic.coefficient_x + - analytic.coefficient_y + - analytic.coefficient_z; + compute_homogeneous_ellipsoid_analytic(semi_axis_x, semi_axis_y, semi_axis_z); + const double coefficient_sum = analytic.coefficient_x + analytic.coefficient_y + analytic.coefficient_z; INFO("A_x = " << analytic.coefficient_x); INFO("A_y = " << analytic.coefficient_y); @@ -1661,24 +1334,18 @@ TEST_CASE( mfem::DenseMatrix analytic_quadrupole(3, 3); analytic_quadrupole = 0.0; analytic_quadrupole(0, 0) = - (mass / 5.0) * (2.0 * semi_axis_x * semi_axis_x - - semi_axis_y * semi_axis_y - semi_axis_z * semi_axis_z); + (mass / 5.0) * (2.0 * semi_axis_x * semi_axis_x - semi_axis_y * semi_axis_y - semi_axis_z * semi_axis_z); analytic_quadrupole(1, 1) = - (mass / 5.0) * (2.0 * semi_axis_y * semi_axis_y - - semi_axis_x * semi_axis_x - semi_axis_z * semi_axis_z); + (mass / 5.0) * (2.0 * semi_axis_y * semi_axis_y - semi_axis_x * semi_axis_x - semi_axis_z * semi_axis_z); analytic_quadrupole(2, 2) = - (mass / 5.0) * (2.0 * semi_axis_z * semi_axis_z - - semi_axis_x * semi_axis_x - semi_axis_y * semi_axis_y); + (mass / 5.0) * (2.0 * semi_axis_z * semi_axis_z - semi_axis_x * semi_axis_x - semi_axis_y * semi_axis_y); mfem::DenseMatrix quadrupole_difference(f.Q); quadrupole_difference -= analytic_quadrupole; - const double relative_quadrupole_error = - quadrupole_difference.FNorm() / analytic_quadrupole.FNorm(); + const double relative_quadrupole_error = quadrupole_difference.FNorm() / analytic_quadrupole.FNorm(); INFO("Relative quadrupole error = " << relative_quadrupole_error); - HomogeneousEllipsoidHDivCoefficient analytic_field_coefficient( - *f.mapping, numerical_density, analytic - ); + HomogeneousEllipsoidHDivCoefficient analytic_field_coefficient(*f.mapping, numerical_density, analytic); mfem::ParGridFunction analytic_field_projection(f.gravityFluxFes.get()); analytic_field_projection = 0.0; @@ -1686,14 +1353,12 @@ TEST_CASE( const int attribute = f.mesh->attributes[i]; if (attribute != 3) { - analytic_field_projection.ProjectCoefficient( - analytic_field_coefficient, attribute - ); + analytic_field_projection.ProjectCoefficient(analytic_field_coefficient, attribute); } } - const auto gravity_solution = physics::grav_potential(f, args, rho_grid); - const int quadrature_order = get_gravity_quadrature_order(f); + const auto gravity_solution = physics::grav_potential(f, args, rho_grid); + const int quadrature_order = get_gravity_quadrature_order(f); double local_field_error_squared = 0.0; double local_field_norm_squared = 0.0; double local_projection_error_squared = 0.0; @@ -1715,70 +1380,45 @@ TEST_CASE( continue; } - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(elem_id); - const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); + const mfem::IntegrationRule &integration_rule = + mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < integration_rule.GetNPoints(); ++q) { - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(q); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); - const double map_determinant = - f.mapping->ComputeDetJ(*transformation, integration_point); - MFEM_VERIFY( - map_determinant > 0.0, - "Domain mapping has a non-positive Jacobian determinant." - ); + const double map_determinant = f.mapping->ComputeDetJ(*transformation, integration_point); + MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant."); - const double weight = transformation->Weight() * - integration_point.weight * map_determinant; - f.mapping->GetPhysicalPoint( - *transformation, integration_point, x_physical - ); - gravity_solution.gradPhi.GetVectorValue( - elem_id, integration_point, grad_phi_element - ); + const double weight = transformation->Weight() * integration_point.weight * map_determinant; + f.mapping->GetPhysicalPoint(*transformation, integration_point, x_physical); + gravity_solution.gradPhi.GetVectorValue(elem_id, integration_point, grad_phi_element); f.mapping->ComputeJacobian(*transformation, map_jacobian); map_jacobian.Mult(grad_phi_element, grad_phi_physical); grad_phi_physical /= map_determinant; - analytic_field_projection.GetVectorValue( - elem_id, integration_point, projected_field_element - ); - map_jacobian.Mult( - projected_field_element, projected_field_physical - ); + analytic_field_projection.GetVectorValue(elem_id, integration_point, projected_field_element); + map_jacobian.Mult(projected_field_element, projected_field_physical); projected_field_physical /= map_determinant; - grad_phi_analytic(0) = 2.0 * M_PI * utils::G * numerical_density * - analytic.coefficient_x * x_physical(0); - grad_phi_analytic(1) = 2.0 * M_PI * utils::G * numerical_density * - analytic.coefficient_y * x_physical(1); - grad_phi_analytic(2) = 2.0 * M_PI * utils::G * numerical_density * - analytic.coefficient_z * x_physical(2); + grad_phi_analytic(0) = 2.0 * M_PI * utils::G * numerical_density * analytic.coefficient_x * x_physical(0); + grad_phi_analytic(1) = 2.0 * M_PI * utils::G * numerical_density * analytic.coefficient_y * x_physical(1); + grad_phi_analytic(2) = 2.0 * M_PI * utils::G * numerical_density * analytic.coefficient_z * x_physical(2); projected_field_difference = projected_field_physical; projected_field_difference -= grad_phi_analytic; - local_projection_error_squared += - (projected_field_difference * projected_field_difference) * - weight; + local_projection_error_squared += (projected_field_difference * projected_field_difference) * weight; gravity_projection_difference = grad_phi_physical; gravity_projection_difference -= projected_field_physical; local_gravity_projection_difference_squared += - (gravity_projection_difference * - gravity_projection_difference) * - weight; + (gravity_projection_difference * gravity_projection_difference) * weight; grad_phi_difference = grad_phi_physical; grad_phi_difference -= grad_phi_analytic; - local_field_error_squared += - (grad_phi_difference * grad_phi_difference) * weight; - local_field_norm_squared += - (grad_phi_analytic * grad_phi_analytic) * weight; + local_field_error_squared += (grad_phi_difference * grad_phi_difference) * weight; + local_field_norm_squared += (grad_phi_analytic * grad_phi_analytic) * weight; } } @@ -1786,52 +1426,33 @@ TEST_CASE( double global_field_norm_squared = 0.0; double global_projection_error_squared = 0.0; double global_gravity_projection_difference_squared = 0.0; - MPI_Comm communicator = f.densityFes->GetComm(); + MPI_Comm communicator = f.densityFes->GetComm(); + MPI_Allreduce(&local_field_error_squared, &global_field_error_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_field_norm_squared, &global_field_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( - &local_field_error_squared, &global_field_error_squared, 1, MPI_DOUBLE, + &local_projection_error_squared, &global_projection_error_squared, 1, MPI_DOUBLE, MPI_SUM, communicator + ); + MPI_Allreduce( + &local_gravity_projection_difference_squared, &global_gravity_projection_difference_squared, 1, MPI_DOUBLE, MPI_SUM, communicator ); - MPI_Allreduce( - &local_field_norm_squared, &global_field_norm_squared, 1, MPI_DOUBLE, - MPI_SUM, communicator - ); - MPI_Allreduce( - &local_projection_error_squared, &global_projection_error_squared, 1, - MPI_DOUBLE, MPI_SUM, communicator - ); - MPI_Allreduce( - &local_gravity_projection_difference_squared, - &global_gravity_projection_difference_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); - const double relative_field_error = - std::sqrt(global_field_error_squared / global_field_norm_squared); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_grid, gravity_solution, quadrature_order - ); - const double analytic_binding_energy = - -(3.0 / 10.0) * utils::G * mass * mass * analytic.energy_kernel; + const double relative_field_error = std::sqrt(global_field_error_squared / global_field_norm_squared); + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_grid, gravity_solution, quadrature_order); + const double analytic_binding_energy = -(3.0 / 10.0) * utils::G * mass * mass * analytic.energy_kernel; const double relative_binding_energy_error = - std::abs(energies.binding - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(energies.binding - analytic_binding_energy) / std::abs(analytic_binding_energy); const double relative_virial_energy_error = - std::abs(energies.virial - analytic_binding_energy) / - std::abs(analytic_binding_energy); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); - const double relative_projection_error = - std::sqrt(global_projection_error_squared / global_field_norm_squared); - const double gravity_to_projection_error_ratio = - relative_projection_error > 0.0 - ? relative_field_error / relative_projection_error - : std::numeric_limits::infinity(); - const double relative_gravity_projection_difference = std::sqrt( - global_gravity_projection_difference_squared / global_field_norm_squared - ); - const double projection_gap_ratio = - relative_gravity_projection_difference / relative_projection_error; + std::abs(energies.virial - analytic_binding_energy) / std::abs(analytic_binding_energy); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); + const double relative_projection_error = std::sqrt(global_projection_error_squared / global_field_norm_squared); + const double gravity_to_projection_error_ratio = relative_projection_error > 0.0 + ? relative_field_error / relative_projection_error + : std::numeric_limits::infinity(); + const double relative_gravity_projection_difference = + std::sqrt(global_gravity_projection_difference_squared / global_field_norm_squared); + const double projection_gap_ratio = relative_gravity_projection_difference / relative_projection_error; INFO("Analytic binding energy = " << analytic_binding_energy); INFO("Computed binding energy = " << energies.binding); @@ -1840,17 +1461,11 @@ TEST_CASE( INFO("Relative binding energy error = " << relative_binding_energy_error); INFO("Relative virial energy error = " << relative_virial_energy_error); INFO("Relative virial consistency error = " << relative_consistency_error); - INFO( - "Relative gravity-to-RT-projection difference = " - << relative_gravity_projection_difference - ); + INFO("Relative gravity-to-RT-projection difference = " << relative_gravity_projection_difference); INFO("Gravity-to-projection gap ratio = " << projection_gap_ratio); INFO("Relative RT projection L2 error = " << relative_projection_error); - INFO( - "Gravity-to-projection error ratio = " - << gravity_to_projection_error_ratio - ); + INFO("Gravity-to-projection error ratio = " << gravity_to_projection_error_ratio); REQUIRE(std::isfinite(relative_projection_error)); constexpr double quadrupole_tolerance = 2.0e-4; @@ -1858,25 +1473,11 @@ TEST_CASE( constexpr double energy_tolerance = 1.0e-5; constexpr double consistency_tolerance = 1.0e-5; - CHECK_THAT( - relative_quadrupole_error, - Catch::Matchers::WithinAbs(0.0, quadrupole_tolerance) - ); - CHECK_THAT( - relative_field_error, Catch::Matchers::WithinAbs(0.0, field_tolerance) - ); - CHECK_THAT( - relative_binding_energy_error, - Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_virial_energy_error, - Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, consistency_tolerance) - ); + CHECK_THAT(relative_quadrupole_error, Catch::Matchers::WithinAbs(0.0, quadrupole_tolerance)); + CHECK_THAT(relative_field_error, Catch::Matchers::WithinAbs(0.0, field_tolerance)); + CHECK_THAT(relative_binding_energy_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_virial_energy_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, consistency_tolerance)); } TEST_CASE( @@ -1897,8 +1498,7 @@ TEST_CASE( const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - auto affine_displacement = [](const mfem::Vector &x, - mfem::Vector &displacement_value) { + auto affine_displacement = [](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(3); displacement_value(0) = (x_scale - 1.0) * x(0); displacement_value(1) = (y_scale - 1.0) * x(1); @@ -1914,32 +1514,24 @@ TEST_CASE( constexpr double concentration = 16.0; const double density_scale = mass / std::pow(radius, 3.0); - auto ellipsoidal_rho = [semi_axis_x, semi_axis_y, semi_axis_z, - density_scale](const mfem::Vector &x) { - const double ellipsoidal_radius_squared = - x(0) * x(0) / (semi_axis_x * semi_axis_x) + - x(1) * x(1) / (semi_axis_y * semi_axis_y) + - x(2) * x(2) / (semi_axis_z * semi_axis_z); + auto ellipsoidal_rho = [semi_axis_x, semi_axis_y, semi_axis_z, density_scale](const mfem::Vector &x) { + const double ellipsoidal_radius_squared = x(0) * x(0) / (semi_axis_x * semi_axis_x) + + x(1) * x(1) / (semi_axis_y * semi_axis_y) + + x(2) * x(2) / (semi_axis_z * semi_axis_z); if (ellipsoidal_radius_squared >= 1.0) { return 0.0; } - const double denominator = - 1.0 + concentration * ellipsoidal_radius_squared; - return density_scale * (1.0 - ellipsoidal_radius_squared) / - (denominator * denominator); + const double denominator = 1.0 + concentration * ellipsoidal_radius_squared; + return density_scale * (1.0 - ellipsoidal_radius_squared) / (denominator * denominator); }; std::unique_ptr rho_coeff; if (f.has_mapping()) { - rho_coeff = - std::make_unique( - *f.mapping, ellipsoidal_rho - ); + rho_coeff = std::make_unique(*f.mapping, ellipsoidal_rho); } else { - rho_coeff = - std::make_unique(ellipsoidal_rho); + rho_coeff = std::make_unique(ellipsoidal_rho); } mfem::GridFunction rho_grid(f.densityFes.get()); @@ -1947,8 +1539,8 @@ TEST_CASE( zero_vacuum_density(f, rho_grid); analysis::conserve_mass(f, rho_grid, mass); - f.com = analysis::get_com(f, rho_grid); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); + f.com = analysis::get_com(f, rho_grid); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); const double normalized_quadrupole = f.Q.FNorm() / (mass * radius * radius); INFO("Normalized quadrupole = " << normalized_quadrupole); @@ -1956,31 +1548,24 @@ TEST_CASE( const auto gravity_solution = physics::grav_potential(f, args, rho_grid); const int quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_grid, gravity_solution, quadrature_order - ); + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_grid, gravity_solution, quadrature_order); REQUIRE(energies.binding < 0.0); REQUIRE(energies.virial < 0.0); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); INFO("W_bind = " << energies.binding); INFO("W_vir = " << energies.virial); INFO("Relative virial consistency error = " << relative_consistency_error); constexpr double virial_tolerance = 1.0e-5; - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, virial_tolerance) - ); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, virial_tolerance)); } TEST_CASE( "New Gravity Potential Matches Uniform Sphere Analytic", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -2004,36 +1589,29 @@ TEST_CASE( zero_vacuum_density(f, rho_uniform); analysis::conserve_mass(f, rho_uniform, mass); - f.com = analysis::get_com(f, rho_uniform); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); + f.com = analysis::get_com(f, rho_uniform); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); - const physics::GravitySolution gravity_solution = - physics::grav_potential_new(f, args, rho_uniform, displacement); + const physics::GravitySolution gravity_solution = physics::grav_potential_new(f, args, rho_uniform, displacement); - constexpr double potential_tolerance = - utils::APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING; - double local_maximum_absolute_error = 0.0; - double local_maximum_relative_error = 0.0; + constexpr double potential_tolerance = utils::APPROX_MAX_ACCEPTABLE_POTENTIAL_ERROR_SI_BURNING; + double local_maximum_absolute_error = 0.0; + double local_maximum_relative_error = 0.0; - const int elements_to_test = std::min(30, f.mesh->GetNE()); + const int elements_to_test = std::min(30, f.mesh->GetNE()); for (int element_id = 0; element_id < elements_to_test; ++element_id) { if (f.mesh->GetAttribute(element_id) == 3) { continue; } - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); - const mfem::IntegrationRule &integration_rule = - mfem::IntRules.Get(transformation->GetGeometryType(), 2); - const mfem::IntegrationPoint &integration_point = - integration_rule.IntPoint(0); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); + const mfem::IntegrationRule &integration_rule = mfem::IntRules.Get(transformation->GetGeometryType(), 2); + const mfem::IntegrationPoint &integration_point = integration_rule.IntPoint(0); transformation->SetIntPoint(&integration_point); mfem::Vector physical_position; - f.mapping->GetPhysicalPoint( - *transformation, integration_point, physical_position - ); + f.mapping->GetPhysicalPoint(*transformation, integration_point, physical_position); const double radial_coordinate = physical_position.Norml2(); @@ -2041,60 +1619,40 @@ TEST_CASE( continue; } - const double analytic_potential = - -(utils::G * mass / (2.0 * std::pow(radius, 3.0))) * - (3.0 * radius * radius - radial_coordinate * radial_coordinate); + const double analytic_potential = -(utils::G * mass / (2.0 * std::pow(radius, 3.0))) * + (3.0 * radius * radius - radial_coordinate * radial_coordinate); - const double computed_potential = - gravity_solution.phi.GetValue(element_id, integration_point); - const double absolute_error = - std::abs(computed_potential - analytic_potential); - const double relative_error = - absolute_error / std::abs(analytic_potential); + const double computed_potential = gravity_solution.phi.GetValue(element_id, integration_point); + const double absolute_error = std::abs(computed_potential - analytic_potential); + const double relative_error = absolute_error / std::abs(analytic_potential); - local_maximum_absolute_error = - std::max(local_maximum_absolute_error, absolute_error); - local_maximum_relative_error = - std::max(local_maximum_relative_error, relative_error); + local_maximum_absolute_error = std::max(local_maximum_absolute_error, absolute_error); + local_maximum_relative_error = std::max(local_maximum_relative_error, relative_error); } double global_maximum_absolute_error = 0.0; double global_maximum_relative_error = 0.0; MPI_Allreduce( - &local_maximum_absolute_error, &global_maximum_absolute_error, 1, - MPI_DOUBLE, MPI_MAX, f.densityFes->GetComm() + &local_maximum_absolute_error, &global_maximum_absolute_error, 1, MPI_DOUBLE, MPI_MAX, f.densityFes->GetComm() ); MPI_Allreduce( - &local_maximum_relative_error, &global_maximum_relative_error, 1, - MPI_DOUBLE, MPI_MAX, f.densityFes->GetComm() + &local_maximum_relative_error, &global_maximum_relative_error, 1, MPI_DOUBLE, MPI_MAX, f.densityFes->GetComm() ); - const int quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies energies = compute_gravitational_energies( - f, rho_uniform, gravity_solution, quadrature_order - ); - const double analytic_binding_energy = - -(3.0 / 5.0) * utils::G * mass * mass / radius; + const int quadrature_order = get_gravity_quadrature_order(f); + const GravitationalEnergies energies = + compute_gravitational_energies(f, rho_uniform, gravity_solution, quadrature_order); + const double analytic_binding_energy = -(3.0 / 5.0) * utils::G * mass * mass / radius; const double relative_binding_error = - std::abs(energies.binding - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(energies.binding - analytic_binding_energy) / std::abs(analytic_binding_energy); const double relative_virial_error = - std::abs(energies.virial - analytic_binding_energy) / - std::abs(analytic_binding_energy); - const double relative_consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + std::abs(energies.virial - analytic_binding_energy) / std::abs(analytic_binding_energy); + const double relative_consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); - INFO( - "Global maximum absolute potential error = " - << global_maximum_absolute_error - ); - INFO( - "Global maximum relative potential error = " - << global_maximum_relative_error - ); + INFO("Global maximum absolute potential error = " << global_maximum_absolute_error); + INFO("Global maximum relative potential error = " << global_maximum_relative_error); INFO("Analytic binding energy = " << analytic_binding_energy); INFO("New-solver binding energy = " << energies.binding); INFO("New-solver virial energy = " << energies.virial); @@ -2108,31 +1666,16 @@ TEST_CASE( constexpr double energy_tolerance = 1.0e-5; constexpr double consistency_tolerance = 1.0e-6; - CHECK_THAT( - global_maximum_relative_error, - Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance) - ); - CHECK_THAT( - global_maximum_absolute_error, - Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance) - ); - CHECK_THAT( - relative_binding_error, - Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_virial_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance) - ); - CHECK_THAT( - relative_consistency_error, - Catch::Matchers::WithinAbs(0.0, consistency_tolerance) - ); + CHECK_THAT(global_maximum_relative_error, Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance)); + CHECK_THAT(global_maximum_absolute_error, Catch::Matchers::WithinAbs(0.0, 0.1 * potential_tolerance)); + CHECK_THAT(relative_binding_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_virial_error, Catch::Matchers::WithinAbs(0.0, energy_tolerance)); + CHECK_THAT(relative_consistency_error, Catch::Matchers::WithinAbs(0.0, consistency_tolerance)); } TEST_CASE( "New Gravity Potential Matches Legacy Solver On Homogeneous Ellipsoid", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration &tags::legacy_comparison + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::legacy_comparison ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -2147,25 +1690,20 @@ TEST_CASE( constexpr double y_scale = 0.95; constexpr double z_scale = 1.0 / (x_scale * y_scale); - REQUIRE_THAT( - x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14) - ); + REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14)); const double semi_axis_x = x_scale * radius; const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - auto affine_displacement = [](const mfem::Vector &position, - mfem::Vector &value) { + auto affine_displacement = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = (x_scale - 1.0) * position(0); value(1) = (y_scale - 1.0) * position(1); value(2) = (z_scale - 1.0) * position(2); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, affine_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, affine_displacement); mfem::ParGridFunction displacement(f.displacementFes.get()); displacement.ProjectCoefficient(displacement_coefficient); @@ -2176,83 +1714,69 @@ TEST_CASE( REQUIRE(f.gravityContext.source_form != nullptr); REQUIRE(f.domainMapperStateless != nullptr); - const double analytic_volume = - (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; - const double density = mass / analytic_volume; + const double analytic_volume = (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; + const double density = mass / analytic_volume; mfem::GridFunction rho_grid(f.densityFes.get()); rho_grid = density; zero_vacuum_density(f, rho_grid); - const double projected_mass = analysis::domain_integrate_grid_function( - f, rho_grid, utils::DOMAINS::STELLAR - ); + const double projected_mass = analysis::domain_integrate_grid_function(f, rho_grid, utils::DOMAINS::STELLAR); const double numerical_density = density * mass / projected_mass; analysis::conserve_mass(f, rho_grid, mass); - f.com = analysis::get_com(f, rho_grid); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); + f.com = analysis::get_com(f, rho_grid); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_grid, f.com); - const physics::GravitySolution legacy_solution = - physics::grav_potential(f, args, rho_grid); - const physics::GravitySolution new_solution = - physics::grav_potential_new(f, args, rho_grid, displacement); + const physics::GravitySolution legacy_solution = physics::grav_potential(f, args, rho_grid); + const physics::GravitySolution new_solution = physics::grav_potential_new(f, args, rho_grid, displacement); - using gravity_form = utils::blocks::gravity_field_form; + using gravity_form = utils::blocks::gravity_field_form; constexpr auto gravity_gradient_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.gradient_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.gradient_term); constexpr auto gravity_poisson_residual_block = - utils::blocks::get_residual_block( - utils::blocks::gravity_field.poisson_term - ); + utils::blocks::get_residual_block(utils::blocks::gravity_field.poisson_term); const std::array value_sizes{ - f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), - f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() + f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(), f.gravityFluxFes->GetTrueVSize(), + f.gravityPotentialFes->GetTrueVSize() }; const std::array residual_sizes{ f.gravityFluxFes->GetTrueVSize(), f.gravityPotentialFes->GetTrueVSize() }; - const utils::blocks::form_layout gravity_layout( - value_sizes, residual_sizes - ); - const int gravity_system_size = gravity_layout.residual_offsets().Last(); - const int gravity_gradient_size = - gravity_layout.size(gravity_gradient_residual_block); - const int gravity_poisson_size = - gravity_layout.size(gravity_poisson_residual_block); + const utils::blocks::form_layout gravity_layout(value_sizes, residual_sizes); + const int gravity_system_size = gravity_layout.residual_offsets().Last(); + const int gravity_gradient_size = gravity_layout.size(gravity_gradient_residual_block); + const int gravity_poisson_size = gravity_layout.size(gravity_poisson_residual_block); mfem::Vector density_true; mfem::Vector displacement_true; gravity_test_get_true_dofs(*f.densityFes, rho_grid, density_true); - gravity_test_get_true_dofs( - *f.displacementFes, displacement, displacement_true - ); + gravity_test_get_true_dofs(*f.displacementFes, displacement, displacement_true); REQUIRE(density_true.Size() == f.densityFes->GetTrueVSize()); REQUIRE(displacement_true.Size() == f.displacementFes->GetTrueVSize()); - operators::context::gravity_field::GravityFieldLinearizationContext - residual_linearization_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldLinearizationContext residual_linearization_context( + f, *f.domainMapperStateless + ); operators::GravityFieldJacobianOperator residual_jacobian( - f, *f.domainMapperStateless, residual_linearization_context, - gravity_layout.value_offsets(), gravity_layout.residual_offsets() + f, *f.domainMapperStateless, residual_linearization_context, gravity_layout.value_offsets(), + gravity_layout.residual_offsets() ); operators::GravityFieldOperator residual_gravity_operator( - f, *f.domainMapperStateless, residual_linearization_context, - gravity_layout.value_offsets(), residual_jacobian + f, *f.domainMapperStateless, residual_linearization_context, gravity_layout.value_offsets(), residual_jacobian ); - operators::context::gravity_field::GravityFieldGeometryContext - residual_geometry_context(f, *f.domainMapperStateless); + operators::context::gravity_field::GravityFieldGeometryContext residual_geometry_context( + f, *f.domainMapperStateless + ); operators::ReducedGravityFieldOperator new_reduced_operator( residual_gravity_operator, residual_geometry_context, displacement_true @@ -2277,18 +1801,15 @@ TEST_CASE( REQUIRE(legacy_source_action.Size() == gravity_poisson_size); mfem::BlockVector legacy_right_hand_side(gravity_layout.residual_offsets()); - legacy_right_hand_side = 0.0; - legacy_right_hand_side.GetBlock(gravity_poisson_residual_block) = - legacy_source_action; + legacy_right_hand_side = 0.0; + legacy_right_hand_side.GetBlock(gravity_poisson_residual_block) = legacy_source_action; REQUIRE(legacy_right_hand_side.Size() == gravity_system_size); - MPI_Comm communicator = f.densityFes->GetComm(); + MPI_Comm communicator = f.densityFes->GetComm(); - const double new_right_hand_side_norm = - gravity_test_global_norm(new_right_hand_side, communicator); - const double legacy_right_hand_side_norm = - gravity_test_global_norm(legacy_right_hand_side, communicator); + const double new_right_hand_side_norm = gravity_test_global_norm(new_right_hand_side, communicator); + const double legacy_right_hand_side_norm = gravity_test_global_norm(legacy_right_hand_side, communicator); REQUIRE(new_right_hand_side_norm > 0.0); REQUIRE(legacy_right_hand_side_norm > 0.0); @@ -2296,14 +1817,11 @@ TEST_CASE( mfem::Vector right_hand_side_difference(new_right_hand_side); right_hand_side_difference -= legacy_right_hand_side; - const double right_hand_side_scale = std::max( - 0.5 * (new_right_hand_side_norm + legacy_right_hand_side_norm), - std::numeric_limits::min() - ); + const double right_hand_side_scale = + std::max(0.5 * (new_right_hand_side_norm + legacy_right_hand_side_norm), std::numeric_limits::min()); const double relative_right_hand_side_difference = - gravity_test_global_norm(right_hand_side_difference, communicator) / - right_hand_side_scale; + gravity_test_global_norm(right_hand_side_difference, communicator) / right_hand_side_scale; mfem::BlockVector legacy_gravity_state(gravity_layout.residual_offsets()); mfem::BlockVector new_gravity_state(gravity_layout.residual_offsets()); @@ -2321,10 +1839,8 @@ TEST_CASE( REQUIRE(gradient_true.Size() == gravity_gradient_size); REQUIRE(potential_true.Size() == gravity_poisson_size); - legacy_gravity_state.GetBlock(gravity_gradient_residual_block) = - gradient_true; - legacy_gravity_state.GetBlock(gravity_poisson_residual_block) = - potential_true; + legacy_gravity_state.GetBlock(gravity_gradient_residual_block) = gradient_true; + legacy_gravity_state.GetBlock(gravity_poisson_residual_block) = potential_true; new_solution.gradPhi.GetTrueDofs(gradient_true); new_solution.phi.GetTrueDofs(potential_true); @@ -2332,10 +1848,8 @@ TEST_CASE( REQUIRE(gradient_true.Size() == gravity_gradient_size); REQUIRE(potential_true.Size() == gravity_poisson_size); - new_gravity_state.GetBlock(gravity_gradient_residual_block) = - gradient_true; - new_gravity_state.GetBlock(gravity_poisson_residual_block) = - potential_true; + new_gravity_state.GetBlock(gravity_gradient_residual_block) = gradient_true; + new_gravity_state.GetBlock(gravity_poisson_residual_block) = potential_true; } REQUIRE(legacy_gravity_state.Size() == gravity_system_size); @@ -2354,17 +1868,11 @@ TEST_CASE( new_action_at_legacy_solution = 0.0; new_action_at_new_solution = 0.0; - f.gravityContext.block_A->Mult( - legacy_gravity_state, legacy_action_at_legacy_solution - ); + f.gravityContext.block_A->Mult(legacy_gravity_state, legacy_action_at_legacy_solution); - f.gravityContext.block_A->Mult( - new_gravity_state, legacy_action_at_new_solution - ); + f.gravityContext.block_A->Mult(new_gravity_state, legacy_action_at_new_solution); - new_reduced_operator.Mult( - legacy_gravity_state, new_action_at_legacy_solution - ); + new_reduced_operator.Mult(legacy_gravity_state, new_action_at_legacy_solution); new_reduced_operator.Mult(new_gravity_state, new_action_at_new_solution); @@ -2373,9 +1881,7 @@ TEST_CASE( REQUIRE(new_action_at_legacy_solution.Size() == gravity_system_size); REQUIRE(new_action_at_new_solution.Size() == gravity_system_size); - mfem::Vector legacy_residual_at_legacy_solution( - legacy_action_at_legacy_solution - ); + mfem::Vector legacy_residual_at_legacy_solution(legacy_action_at_legacy_solution); mfem::Vector legacy_residual_at_new_solution(legacy_action_at_new_solution); mfem::Vector new_residual_at_legacy_solution(new_action_at_legacy_solution); mfem::Vector new_residual_at_new_solution(new_action_at_new_solution); @@ -2386,82 +1892,65 @@ TEST_CASE( new_residual_at_new_solution -= new_right_hand_side; const GravityResidualMetrics legacy_at_legacy = measure_gravity_residual( - legacy_residual_at_legacy_solution, gravity_layout.residual_offsets(), - legacy_right_hand_side_norm, communicator + legacy_residual_at_legacy_solution, gravity_layout.residual_offsets(), legacy_right_hand_side_norm, communicator ); const GravityResidualMetrics legacy_at_new = measure_gravity_residual( - legacy_residual_at_new_solution, gravity_layout.residual_offsets(), - legacy_right_hand_side_norm, communicator + legacy_residual_at_new_solution, gravity_layout.residual_offsets(), legacy_right_hand_side_norm, communicator ); const GravityResidualMetrics new_at_legacy = measure_gravity_residual( - new_residual_at_legacy_solution, gravity_layout.residual_offsets(), - new_right_hand_side_norm, communicator + new_residual_at_legacy_solution, gravity_layout.residual_offsets(), new_right_hand_side_norm, communicator ); const GravityResidualMetrics new_at_new = measure_gravity_residual( - new_residual_at_new_solution, gravity_layout.residual_offsets(), - new_right_hand_side_norm, communicator + new_residual_at_new_solution, gravity_layout.residual_offsets(), new_right_hand_side_norm, communicator ); - mfem::Vector operator_gap_at_legacy_solution( - new_residual_at_legacy_solution - ); + mfem::Vector operator_gap_at_legacy_solution(new_residual_at_legacy_solution); operator_gap_at_legacy_solution -= legacy_residual_at_legacy_solution; mfem::Vector operator_gap_at_new_solution(new_residual_at_new_solution); operator_gap_at_new_solution -= legacy_residual_at_new_solution; const GravityResidualMetrics gap_at_legacy = measure_gravity_residual( - operator_gap_at_legacy_solution, gravity_layout.residual_offsets(), - right_hand_side_scale, communicator + operator_gap_at_legacy_solution, gravity_layout.residual_offsets(), right_hand_side_scale, communicator ); const GravityResidualMetrics gap_at_new = measure_gravity_residual( - operator_gap_at_new_solution, gravity_layout.residual_offsets(), - right_hand_side_scale, communicator + operator_gap_at_new_solution, gravity_layout.residual_offsets(), right_hand_side_scale, communicator ); + INFO("New/legacy right-hand-side difference = " << relative_right_hand_side_difference); INFO( - "New/legacy right-hand-side difference = " - << relative_right_hand_side_difference + "Legacy operator at legacy solution: total = " << legacy_at_legacy.relative_total + << ", gradient = " << legacy_at_legacy.relative_gradient + << ", Poisson = " << legacy_at_legacy.relative_poisson ); INFO( - "Legacy operator at legacy solution: total = " - << legacy_at_legacy.relative_total - << ", gradient = " << legacy_at_legacy.relative_gradient - << ", Poisson = " << legacy_at_legacy.relative_poisson + "Legacy operator at new solution: total = " << legacy_at_new.relative_total + << ", gradient = " << legacy_at_new.relative_gradient + << ", Poisson = " << legacy_at_new.relative_poisson ); INFO( - "Legacy operator at new solution: total = " - << legacy_at_new.relative_total - << ", gradient = " << legacy_at_new.relative_gradient - << ", Poisson = " << legacy_at_new.relative_poisson + "New operator at legacy solution: total = " << new_at_legacy.relative_total + << ", gradient = " << new_at_legacy.relative_gradient + << ", Poisson = " << new_at_legacy.relative_poisson ); INFO( - "New operator at legacy solution: total = " - << new_at_legacy.relative_total - << ", gradient = " << new_at_legacy.relative_gradient - << ", Poisson = " << new_at_legacy.relative_poisson + "New operator at new solution: total = " << new_at_new.relative_total + << ", gradient = " << new_at_new.relative_gradient + << ", Poisson = " << new_at_new.relative_poisson ); INFO( - "New operator at new solution: total = " - << new_at_new.relative_total - << ", gradient = " << new_at_new.relative_gradient - << ", Poisson = " << new_at_new.relative_poisson + "Operator gap at legacy solution: total = " << gap_at_legacy.relative_total + << ", gradient = " << gap_at_legacy.relative_gradient + << ", Poisson = " << gap_at_legacy.relative_poisson ); INFO( - "Operator gap at legacy solution: total = " - << gap_at_legacy.relative_total - << ", gradient = " << gap_at_legacy.relative_gradient - << ", Poisson = " << gap_at_legacy.relative_poisson - ); - INFO( - "Operator gap at new solution: total = " - << gap_at_new.relative_total - << ", gradient = " << gap_at_new.relative_gradient - << ", Poisson = " << gap_at_new.relative_poisson + "Operator gap at new solution: total = " << gap_at_new.relative_total + << ", gradient = " << gap_at_new.relative_gradient + << ", Poisson = " << gap_at_new.relative_poisson ); REQUIRE(std::isfinite(legacy_at_legacy.relative_total)); @@ -2475,80 +1964,52 @@ TEST_CASE( constexpr double diagonal_residual_tolerance = 1.0e-8; constexpr double poisson_gap_tolerance = 1.0e-11; - CHECK_THAT( - relative_right_hand_side_difference, - Catch::Matchers::WithinAbs(0.0, source_parity_tolerance) - ); + CHECK_THAT(relative_right_hand_side_difference, Catch::Matchers::WithinAbs(0.0, source_parity_tolerance)); - CHECK_THAT( - legacy_at_legacy.relative_total, - Catch::Matchers::WithinAbs(0.0, diagonal_residual_tolerance) - ); + CHECK_THAT(legacy_at_legacy.relative_total, Catch::Matchers::WithinAbs(0.0, diagonal_residual_tolerance)); - CHECK_THAT( - new_at_new.relative_total, - Catch::Matchers::WithinAbs(0.0, diagonal_residual_tolerance) - ); + CHECK_THAT(new_at_new.relative_total, Catch::Matchers::WithinAbs(0.0, diagonal_residual_tolerance)); - CHECK_THAT( - gap_at_legacy.relative_poisson, - Catch::Matchers::WithinAbs(0.0, poisson_gap_tolerance) - ); + CHECK_THAT(gap_at_legacy.relative_poisson, Catch::Matchers::WithinAbs(0.0, poisson_gap_tolerance)); - CHECK_THAT( - gap_at_new.relative_poisson, - Catch::Matchers::WithinAbs(0.0, poisson_gap_tolerance) - ); + CHECK_THAT(gap_at_new.relative_poisson, Catch::Matchers::WithinAbs(0.0, poisson_gap_tolerance)); const int quadrature_order = get_gravity_quadrature_order(f); - const GravitySolutionComparison comparison = compare_gravity_solutions( - f, legacy_solution, new_solution, quadrature_order - ); + const GravitySolutionComparison comparison = + compare_gravity_solutions(f, legacy_solution, new_solution, quadrature_order); const GravitationalEnergies legacy_energies = - compute_gravitational_energies( - f, rho_grid, legacy_solution, quadrature_order - ); + compute_gravitational_energies(f, rho_grid, legacy_solution, quadrature_order); - const GravitationalEnergies new_energies = compute_gravitational_energies( - f, rho_grid, new_solution, quadrature_order - ); + const GravitationalEnergies new_energies = + compute_gravitational_energies(f, rho_grid, new_solution, quadrature_order); const HomogeneousEllipsoidAnalytic analytic = - compute_homogeneous_ellipsoid_analytic( - semi_axis_x, semi_axis_y, semi_axis_z - ); + compute_homogeneous_ellipsoid_analytic(semi_axis_x, semi_axis_y, semi_axis_z); - const double analytic_binding_energy = - -(3.0 / 10.0) * utils::G * mass * mass * analytic.energy_kernel; + const double analytic_binding_energy = -(3.0 / 10.0) * utils::G * mass * mass * analytic.energy_kernel; const double new_binding_analytic_error = - std::abs(new_energies.binding - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(new_energies.binding - analytic_binding_energy) / std::abs(analytic_binding_energy); const double new_virial_analytic_error = - std::abs(new_energies.virial - analytic_binding_energy) / - std::abs(analytic_binding_energy); + std::abs(new_energies.virial - analytic_binding_energy) / std::abs(analytic_binding_energy); const double new_virial_consistency_error = - std::abs(new_energies.binding - new_energies.virial) / - std::abs(new_energies.binding); + std::abs(new_energies.binding - new_energies.virial) / std::abs(new_energies.binding); const double relative_binding_difference = std::abs(new_energies.binding - legacy_energies.binding) / std::max( - 0.5 * (std::abs(new_energies.binding) + - std::abs(legacy_energies.binding)), + 0.5 * (std::abs(new_energies.binding) + std::abs(legacy_energies.binding)), std::numeric_limits::min() ); const double relative_virial_difference = std::abs(new_energies.virial - legacy_energies.virial) / std::max( - 0.5 * (std::abs(new_energies.virial) + - std::abs(legacy_energies.virial)), - std::numeric_limits::min() + 0.5 * (std::abs(new_energies.virial) + std::abs(legacy_energies.virial)), std::numeric_limits::min() ); INFO("Numerical density = " << numerical_density); @@ -2560,20 +2021,10 @@ TEST_CASE( INFO("New binding analytic error = " << new_binding_analytic_error); INFO("New virial analytic error = " << new_virial_analytic_error); INFO("New virial consistency error = " << new_virial_consistency_error); - INFO( - "New/legacy physical gradient difference = " - << comparison.relative_gradient_difference - ); - INFO( - "New/legacy physical potential difference = " - << comparison.relative_potential_difference - ); - INFO( - "New/legacy binding-energy difference = " << relative_binding_difference - ); - INFO( - "New/legacy virial-energy difference = " << relative_virial_difference - ); + INFO("New/legacy physical gradient difference = " << comparison.relative_gradient_difference); + INFO("New/legacy physical potential difference = " << comparison.relative_potential_difference); + INFO("New/legacy binding-energy difference = " << relative_binding_difference); + INFO("New/legacy virial-energy difference = " << relative_virial_difference); REQUIRE(legacy_energies.binding < 0.0); REQUIRE(legacy_energies.virial < 0.0); @@ -2589,46 +2040,24 @@ TEST_CASE( constexpr double analytic_energy_tolerance = 1.0e-5; constexpr double consistency_tolerance = 1.0e-5; - CHECK_THAT( - comparison.relative_gradient_difference, - Catch::Matchers::WithinAbs(0.0, gradient_parity_tolerance) - ); + CHECK_THAT(comparison.relative_gradient_difference, Catch::Matchers::WithinAbs(0.0, gradient_parity_tolerance)); - CHECK_THAT( - comparison.relative_potential_difference, - Catch::Matchers::WithinAbs(0.0, potential_parity_tolerance) - ); + CHECK_THAT(comparison.relative_potential_difference, Catch::Matchers::WithinAbs(0.0, potential_parity_tolerance)); - CHECK_THAT( - relative_binding_difference, - Catch::Matchers::WithinAbs(0.0, energy_parity_tolerance) - ); + CHECK_THAT(relative_binding_difference, Catch::Matchers::WithinAbs(0.0, energy_parity_tolerance)); - CHECK_THAT( - relative_virial_difference, - Catch::Matchers::WithinAbs(0.0, energy_parity_tolerance) - ); + CHECK_THAT(relative_virial_difference, Catch::Matchers::WithinAbs(0.0, energy_parity_tolerance)); - CHECK_THAT( - new_binding_analytic_error, - Catch::Matchers::WithinAbs(0.0, analytic_energy_tolerance) - ); + CHECK_THAT(new_binding_analytic_error, Catch::Matchers::WithinAbs(0.0, analytic_energy_tolerance)); - CHECK_THAT( - new_virial_analytic_error, - Catch::Matchers::WithinAbs(0.0, analytic_energy_tolerance) - ); + CHECK_THAT(new_virial_analytic_error, Catch::Matchers::WithinAbs(0.0, analytic_energy_tolerance)); - CHECK_THAT( - new_virial_consistency_error, - Catch::Matchers::WithinAbs(0.0, consistency_tolerance) - ); + CHECK_THAT(new_virial_consistency_error, Catch::Matchers::WithinAbs(0.0, consistency_tolerance)); } TEST_CASE( "New Gravity Potential Deformed Rational Density Virial Self-Consistency", - tags::gravity &tags::self_consistency &tags::initialization - &tags::integration + tags::gravity &tags::self_consistency &tags::initialization &tags::integration ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -2643,25 +2072,20 @@ TEST_CASE( constexpr double y_scale = 0.95; constexpr double z_scale = 1.0 / (x_scale * y_scale); - REQUIRE_THAT( - x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14) - ); + REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14)); const double semi_axis_x = x_scale * radius; const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - auto affine_displacement = [](const mfem::Vector &position, - mfem::Vector &value) { + auto affine_displacement = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = (x_scale - 1.0) * position(0); value(1) = (y_scale - 1.0) * position(1); value(2) = (z_scale - 1.0) * position(2); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, affine_displacement - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, affine_displacement); mfem::ParGridFunction displacement(f.displacementFes.get()); @@ -2673,27 +2097,21 @@ TEST_CASE( constexpr double concentration = 16.0; const double density_scale = mass / std::pow(radius, 3.0); - auto ellipsoidal_density = [semi_axis_x, semi_axis_y, semi_axis_z, - density_scale](const mfem::Vector &position) { - const double ellipsoidal_radius_squared = - position(0) * position(0) / (semi_axis_x * semi_axis_x) + - position(1) * position(1) / (semi_axis_y * semi_axis_y) + - position(2) * position(2) / (semi_axis_z * semi_axis_z); + auto ellipsoidal_density = [semi_axis_x, semi_axis_y, semi_axis_z, density_scale](const mfem::Vector &position) { + const double ellipsoidal_radius_squared = position(0) * position(0) / (semi_axis_x * semi_axis_x) + + position(1) * position(1) / (semi_axis_y * semi_axis_y) + + position(2) * position(2) / (semi_axis_z * semi_axis_z); if (ellipsoidal_radius_squared >= 1.0) { return 0.0; } - const double denominator = - 1.0 + concentration * ellipsoidal_radius_squared; + const double denominator = 1.0 + concentration * ellipsoidal_radius_squared; - return density_scale * (1.0 - ellipsoidal_radius_squared) / - (denominator * denominator); + return density_scale * (1.0 - ellipsoidal_radius_squared) / (denominator * denominator); }; - mapping::PhysicalPositionFunctionCoefficient density_coefficient( - *f.mapping, ellipsoidal_density - ); + mapping::PhysicalPositionFunctionCoefficient density_coefficient(*f.mapping, ellipsoidal_density); mfem::GridFunction density(f.densityFes.get()); @@ -2702,8 +2120,8 @@ TEST_CASE( zero_vacuum_density(f, density); analysis::conserve_mass(f, density, mass); - f.com = analysis::get_com(f, density); - f.Q = physics::compute_quadrupole_moment_tensor(f, density, f.com); + f.com = analysis::get_com(f, density); + f.Q = physics::compute_quadrupole_moment_tensor(f, density, f.com); const double normalized_quadrupole = f.Q.FNorm() / (mass * radius * radius); @@ -2711,46 +2129,36 @@ TEST_CASE( REQUIRE(normalized_quadrupole > 1.0e-3); - const physics::GravitySolution gravity_solution = - physics::grav_potential_new(f, args, density, displacement); + const physics::GravitySolution gravity_solution = physics::grav_potential_new(f, args, density, displacement); - const int base_quadrature_order = get_gravity_quadrature_order(f); + const int base_quadrature_order = get_gravity_quadrature_order(f); - const GravitationalEnergies base_energies = compute_gravitational_energies( - f, density, gravity_solution, base_quadrature_order - ); + const GravitationalEnergies base_energies = + compute_gravitational_energies(f, density, gravity_solution, base_quadrature_order); const GravitationalEnergies medium_energies = - compute_gravitational_energies( - f, density, gravity_solution, base_quadrature_order + 4 - ); + compute_gravitational_energies(f, density, gravity_solution, base_quadrature_order + 4); - const GravitationalEnergies fine_energies = compute_gravitational_energies( - f, density, gravity_solution, base_quadrature_order + 8 - ); + const GravitationalEnergies fine_energies = + compute_gravitational_energies(f, density, gravity_solution, base_quadrature_order + 8); REQUIRE(fine_energies.binding < 0.0); REQUIRE(fine_energies.virial < 0.0); const double base_consistency_error = - std::abs(base_energies.binding - base_energies.virial) / - std::abs(base_energies.binding); + std::abs(base_energies.binding - base_energies.virial) / std::abs(base_energies.binding); const double medium_consistency_error = - std::abs(medium_energies.binding - medium_energies.virial) / - std::abs(medium_energies.binding); + std::abs(medium_energies.binding - medium_energies.virial) / std::abs(medium_energies.binding); const double fine_consistency_error = - std::abs(fine_energies.binding - fine_energies.virial) / - std::abs(fine_energies.binding); + std::abs(fine_energies.binding - fine_energies.virial) / std::abs(fine_energies.binding); const double binding_quadrature_change = - std::abs(fine_energies.binding - medium_energies.binding) / - std::abs(fine_energies.binding); + std::abs(fine_energies.binding - medium_energies.binding) / std::abs(fine_energies.binding); const double virial_quadrature_change = - std::abs(fine_energies.virial - medium_energies.virial) / - std::abs(fine_energies.virial); + std::abs(fine_energies.virial - medium_energies.virial) / std::abs(fine_energies.virial); INFO("Base-order consistency error = " << base_consistency_error); @@ -2758,36 +2166,24 @@ TEST_CASE( INFO("Fine-order consistency error = " << fine_consistency_error); - INFO( - "Medium-to-fine binding-energy change = " << binding_quadrature_change - ); + INFO("Medium-to-fine binding-energy change = " << binding_quadrature_change); INFO("Medium-to-fine virial-energy change = " << virial_quadrature_change); constexpr double virial_tolerance = 1.0e-5; constexpr double diagnostic_quadrature_tolerance = 1.0e-7; - CHECK_THAT( - fine_consistency_error, - Catch::Matchers::WithinAbs(0.0, virial_tolerance) - ); + CHECK_THAT(fine_consistency_error, Catch::Matchers::WithinAbs(0.0, virial_tolerance)); - CHECK_THAT( - binding_quadrature_change, - Catch::Matchers::WithinAbs(0.0, diagnostic_quadrature_tolerance) - ); + CHECK_THAT(binding_quadrature_change, Catch::Matchers::WithinAbs(0.0, diagnostic_quadrature_tolerance)); - CHECK_THAT( - virial_quadrature_change, - Catch::Matchers::WithinAbs(0.0, diagnostic_quadrature_tolerance) - ); + CHECK_THAT(virial_quadrature_change, Catch::Matchers::WithinAbs(0.0, diagnostic_quadrature_tolerance)); } TEST_CASE( "New Gravity Potential Resolves Exterior Monopole By Compactification " "Shell", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -2799,11 +2195,10 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.compactificationCoordinate != nullptr); - const double radius = utils::RADIUS; - const double mass = utils::MASS; - const double analytic_volume = - (4.0 / 3.0) * M_PI * radius * radius * radius; - const double density = mass / analytic_volume; + const double radius = utils::RADIUS; + const double mass = utils::MASS; + const double analytic_volume = (4.0 / 3.0) * M_PI * radius * radius * radius; + const double density = mass / analytic_volume; mfem::ParGridFunction displacement(f.displacementFes.get()); displacement = 0.0; @@ -2817,26 +2212,18 @@ TEST_CASE( zero_vacuum_density(f, rho_uniform); analysis::conserve_mass(f, rho_uniform, mass); - f.com = analysis::get_com(f, rho_uniform); - f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); + f.com = analysis::get_com(f, rho_uniform); + f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); - const physics::GravitySolution legacy_solution = - physics::grav_potential(f, args, rho_uniform); + const physics::GravitySolution legacy_solution = physics::grav_potential(f, args, rho_uniform); - const physics::GravitySolution new_solution = - physics::grav_potential_new(f, args, rho_uniform, displacement); + const physics::GravitySolution new_solution = physics::grav_potential_new(f, args, rho_uniform, displacement); const std::array legacy_metrics = - measure_exterior_monopole_shells( - f, legacy_solution, displacement, ExteriorMonopoleMapping::legacy, - mass - ); + measure_exterior_monopole_shells(f, legacy_solution, displacement, ExteriorMonopoleMapping::legacy, mass); const std::array new_metrics = - measure_exterior_monopole_shells( - f, new_solution, displacement, ExteriorMonopoleMapping::stateless, - mass - ); + measure_exterior_monopole_shells(f, new_solution, displacement, ExteriorMonopoleMapping::stateless, mass); double maximum_new_potential_error = 0.0; double maximum_new_radial_field_error = 0.0; @@ -2844,73 +2231,40 @@ TEST_CASE( for (int shell = 0; shell < 5; ++shell) { DYNAMIC_SECTION( - "Exterior coordinate in [" - << exterior_shell_boundaries[shell] << ", " - << exterior_shell_boundaries[shell + 1] << ")" + "Exterior coordinate in [" << exterior_shell_boundaries[shell] << ", " + << exterior_shell_boundaries[shell + 1] << ")" ) { const ExteriorMonopoleShellMetrics &legacy = legacy_metrics[shell]; const ExteriorMonopoleShellMetrics ¤t = new_metrics[shell]; - maximum_new_potential_error = std::max( - maximum_new_potential_error, current.potential_rms_error - ); + maximum_new_potential_error = std::max(maximum_new_potential_error, current.potential_rms_error); - maximum_new_radial_field_error = std::max( - maximum_new_radial_field_error, current.radial_field_rms_error - ); + maximum_new_radial_field_error = std::max(maximum_new_radial_field_error, current.radial_field_rms_error); - maximum_new_tangential_field = std::max( - maximum_new_tangential_field, current.tangential_field_rms - ); + maximum_new_tangential_field = std::max(maximum_new_tangential_field, current.tangential_field_rms); INFO("Shell = " << shell); INFO( - "Exterior-coordinate interval = [" - << exterior_shell_boundaries[shell] << ", " - << exterior_shell_boundaries[shell + 1] << ")" + "Exterior-coordinate interval = [" << exterior_shell_boundaries[shell] << ", " + << exterior_shell_boundaries[shell + 1] << ")" ); - INFO( - "Legacy physical-radius range = [" - << legacy.minimum_radius << ", " << legacy.maximum_radius << "]" - ); + INFO("Legacy physical-radius range = [" << legacy.minimum_radius << ", " << legacy.maximum_radius << "]"); - INFO( - "New physical-radius range = [" - << current.minimum_radius << ", " << current.maximum_radius - << "]" - ); + INFO("New physical-radius range = [" << current.minimum_radius << ", " << current.maximum_radius << "]"); - INFO( - "Legacy scaled-potential RMS error = " - << legacy.potential_rms_error - ); + INFO("Legacy scaled-potential RMS error = " << legacy.potential_rms_error); - INFO( - "New scaled-potential RMS error = " - << current.potential_rms_error - ); + INFO("New scaled-potential RMS error = " << current.potential_rms_error); - INFO( - "Legacy scaled-radial-field RMS error = " - << legacy.radial_field_rms_error - ); + INFO("Legacy scaled-radial-field RMS error = " << legacy.radial_field_rms_error); - INFO( - "New scaled-radial-field RMS error = " - << current.radial_field_rms_error - ); + INFO("New scaled-radial-field RMS error = " << current.radial_field_rms_error); - INFO( - "Legacy scaled-tangential-field RMS = " - << legacy.tangential_field_rms - ); + INFO("Legacy scaled-tangential-field RMS = " << legacy.tangential_field_rms); - INFO( - "New scaled-tangential-field RMS = " - << current.tangential_field_rms - ); + INFO("New scaled-tangential-field RMS = " << current.tangential_field_rms); REQUIRE(legacy.quadrature_points > 0); REQUIRE(current.quadrature_points > 0); @@ -2936,38 +2290,22 @@ TEST_CASE( } for (int shell = 1; shell < 5; ++shell) { - CHECK( - new_metrics[shell].minimum_radius >= - new_metrics[shell - 1].minimum_radius - ); + CHECK(new_metrics[shell].minimum_radius >= new_metrics[shell - 1].minimum_radius); - CHECK( - new_metrics[shell].maximum_radius > - new_metrics[shell - 1].maximum_radius - ); + CHECK(new_metrics[shell].maximum_radius > new_metrics[shell - 1].maximum_radius); } - INFO( - "Maximum new scaled-potential shell error = " - << maximum_new_potential_error - ); + INFO("Maximum new scaled-potential shell error = " << maximum_new_potential_error); - INFO( - "Maximum new scaled-radial-field shell error = " - << maximum_new_radial_field_error - ); + INFO("Maximum new scaled-radial-field shell error = " << maximum_new_radial_field_error); - INFO( - "Maximum new scaled-tangential-field shell amplitude = " - << maximum_new_tangential_field - ); + INFO("Maximum new scaled-tangential-field shell amplitude = " << maximum_new_tangential_field); } TEST_CASE( "New Exterior Monopole Error Is Separated From Finite Element Projection " "Floor", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration &tags::accuracy + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::accuracy ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -2979,14 +2317,13 @@ TEST_CASE( REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.compactificationCoordinate != nullptr); - const double stellar_radius = utils::RADIUS; + const double stellar_radius = utils::RADIUS; - const double mass = utils::MASS; + const double mass = utils::MASS; - const double analytic_volume = - (4.0 / 3.0) * M_PI * stellar_radius * stellar_radius * stellar_radius; + const double analytic_volume = (4.0 / 3.0) * M_PI * stellar_radius * stellar_radius * stellar_radius; - const double density = mass / analytic_volume; + const double density = mass / analytic_volume; mfem::ParGridFunction displacement(f.displacementFes.get()); displacement = 0.0; @@ -3005,8 +2342,7 @@ TEST_CASE( f.Q = physics::compute_quadrupole_moment_tensor(f, rho_uniform, f.com); - const physics::GravitySolution numerical_solution = - physics::grav_potential_new(f, args, rho_uniform, displacement); + const physics::GravitySolution numerical_solution = physics::grav_potential_new(f, args, rho_uniform, displacement); StatelessMonopolePotentialCoefficient analytic_potential_coefficient( f, *f.domainMapperStateless, displacement, mass, stellar_radius @@ -3025,16 +2361,11 @@ TEST_CASE( analytic_projection.gradPhi.ProjectCoefficient(analytic_field_coefficient); const std::array numerical_metrics = - measure_exterior_monopole_shells( - f, numerical_solution, displacement, - ExteriorMonopoleMapping::stateless, mass - ); + measure_exterior_monopole_shells(f, numerical_solution, displacement, ExteriorMonopoleMapping::stateless, mass); - const std::array projection_metrics = - measure_exterior_monopole_shells( - f, analytic_projection, displacement, - ExteriorMonopoleMapping::stateless, mass - ); + const std::array projection_metrics = measure_exterior_monopole_shells( + f, analytic_projection, displacement, ExteriorMonopoleMapping::stateless, mass + ); mfem::Vector numerical_gradient_true; mfem::Vector numerical_potential_true; @@ -3049,15 +2380,13 @@ TEST_CASE( analytic_projection.phi.GetTrueDofs(projected_potential_true); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); - const double gradient_projection_gap = global_relative_vector_error( - numerical_gradient_true, projected_gradient_true, communicator - ); + const double gradient_projection_gap = + global_relative_vector_error(numerical_gradient_true, projected_gradient_true, communicator); - const double potential_projection_gap = global_relative_vector_error( - numerical_potential_true, projected_potential_true, communicator - ); + const double potential_projection_gap = + global_relative_vector_error(numerical_potential_true, projected_potential_true, communicator); double maximum_numerical_potential_error = 0.0; double maximum_projected_potential_error = 0.0; @@ -3068,59 +2397,37 @@ TEST_CASE( std::ostringstream report; - report << "Global numerical/projection gradient DOF gap = " - << gradient_projection_gap << '\n' - << "Global numerical/projection potential DOF gap = " - << potential_projection_gap << '\n'; + report << "Global numerical/projection gradient DOF gap = " << gradient_projection_gap << '\n' + << "Global numerical/projection potential DOF gap = " << potential_projection_gap << '\n'; for (int shell = 0; shell < 5; ++shell) { - const ExteriorMonopoleShellMetrics &numerical = - numerical_metrics[shell]; + const ExteriorMonopoleShellMetrics &numerical = numerical_metrics[shell]; - const ExteriorMonopoleShellMetrics &projected = - projection_metrics[shell]; + const ExteriorMonopoleShellMetrics &projected = projection_metrics[shell]; - maximum_numerical_potential_error = std::max( - maximum_numerical_potential_error, numerical.potential_rms_error - ); + maximum_numerical_potential_error = std::max(maximum_numerical_potential_error, numerical.potential_rms_error); - maximum_projected_potential_error = std::max( - maximum_projected_potential_error, projected.potential_rms_error - ); + maximum_projected_potential_error = std::max(maximum_projected_potential_error, projected.potential_rms_error); - maximum_numerical_radial_error = std::max( - maximum_numerical_radial_error, numerical.radial_field_rms_error - ); + maximum_numerical_radial_error = std::max(maximum_numerical_radial_error, numerical.radial_field_rms_error); - maximum_projected_radial_error = std::max( - maximum_projected_radial_error, projected.radial_field_rms_error - ); + maximum_projected_radial_error = std::max(maximum_projected_radial_error, projected.radial_field_rms_error); - maximum_numerical_tangential_field = std::max( - maximum_numerical_tangential_field, numerical.tangential_field_rms - ); + maximum_numerical_tangential_field = + std::max(maximum_numerical_tangential_field, numerical.tangential_field_rms); - maximum_projected_tangential_field = std::max( - maximum_projected_tangential_field, projected.tangential_field_rms - ); + maximum_projected_tangential_field = + std::max(maximum_projected_tangential_field, projected.tangential_field_rms); - report << "Shell " << shell << " xi=[" - << exterior_shell_boundaries[shell] << ", " + report << "Shell " << shell << " xi=[" << exterior_shell_boundaries[shell] << ", " << exterior_shell_boundaries[shell + 1] << "):\n" - << " radius range = [" << numerical.minimum_radius << ", " - << numerical.maximum_radius << "]\n" - << " numerical potential error = " - << numerical.potential_rms_error << '\n' - << " projected potential error = " - << projected.potential_rms_error << '\n' - << " numerical radial-field error = " - << numerical.radial_field_rms_error << '\n' - << " projected radial-field error = " - << projected.radial_field_rms_error << '\n' - << " numerical tangential field = " - << numerical.tangential_field_rms << '\n' - << " projected tangential field = " - << projected.tangential_field_rms << '\n'; + << " radius range = [" << numerical.minimum_radius << ", " << numerical.maximum_radius << "]\n" + << " numerical potential error = " << numerical.potential_rms_error << '\n' + << " projected potential error = " << projected.potential_rms_error << '\n' + << " numerical radial-field error = " << numerical.radial_field_rms_error << '\n' + << " projected radial-field error = " << projected.radial_field_rms_error << '\n' + << " numerical tangential field = " << numerical.tangential_field_rms << '\n' + << " projected tangential field = " << projected.tangential_field_rms << '\n'; } INFO(report.str()); @@ -3159,8 +2466,7 @@ TEST_CASE( TEST_CASE( "New Gravity Potential Matches Analytic Interior Potential For A Deformed " "Homogeneous Star", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration &tags::accuracy + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::accuracy ) { auto args = test_utils::setup_args(); args.p.rtol = 1.0e-13; @@ -3183,38 +2489,30 @@ TEST_CASE( const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - REQUIRE_THAT( - x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14) - ); + REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14)); - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = (x_scale - 1.0) * position(0); value(1) = (y_scale - 1.0) * position(1); value(2) = (z_scale - 1.0) * position(2); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, displacement_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function); mfem::ParGridFunction displacement(fem.displacementFes.get()); displacement.ProjectCoefficient(displacement_coefficient); fem.mapping->SetDisplacement(displacement); physics::update_stiffness_matrix(fem); - const double analytic_volume = - (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; - const double density_value = mass / analytic_volume; + const double analytic_volume = (4.0 / 3.0) * M_PI * semi_axis_x * semi_axis_y * semi_axis_z; + const double density_value = mass / analytic_volume; mfem::GridFunction density(fem.densityFes.get()); density = density_value; zero_vacuum_density(fem, density); - const double projected_mass = analysis::domain_integrate_grid_function( - fem, density, utils::DOMAINS::STELLAR - ); + const double projected_mass = analysis::domain_integrate_grid_function(fem, density, utils::DOMAINS::STELLAR); const double numerical_density = density_value * mass / projected_mass; analysis::conserve_mass(fem, density, mass); @@ -3222,35 +2520,27 @@ TEST_CASE( fem.Q = physics::compute_quadrupole_moment_tensor(fem, density, fem.com); const HomogeneousEllipsoidAnalytic analytic = - compute_homogeneous_ellipsoid_analytic( - semi_axis_x, semi_axis_y, semi_axis_z - ); + compute_homogeneous_ellipsoid_analytic(semi_axis_x, semi_axis_y, semi_axis_z); - auto analytic_potential = [numerical_density, semi_axis_x, semi_axis_y, - semi_axis_z, + auto analytic_potential = [numerical_density, semi_axis_x, semi_axis_y, semi_axis_z, analytic](const mfem::Vector &position) { - const double potential_kernel = - semi_axis_x * semi_axis_y * semi_axis_z * analytic.energy_kernel - - analytic.coefficient_x * position(0) * position(0) - - analytic.coefficient_y * position(1) * position(1) - - analytic.coefficient_z * position(2) * position(2); + const double potential_kernel = semi_axis_x * semi_axis_y * semi_axis_z * analytic.energy_kernel - + analytic.coefficient_x * position(0) * position(0) - + analytic.coefficient_y * position(1) * position(1) - + analytic.coefficient_z * position(2) * position(2); return -M_PI * utils::G * numerical_density * potential_kernel; }; - mapping::PhysicalPositionFunctionCoefficient analytic_potential_coefficient( - *fem.mapping, analytic_potential - ); - const int vacuum_attribute = - fem.domainMapperStateless->GetVacuumElementAttribute(); + mapping::PhysicalPositionFunctionCoefficient analytic_potential_coefficient(*fem.mapping, analytic_potential); + const int vacuum_attribute = fem.domainMapperStateless->GetVacuumElementAttribute(); mfem::ParGridFunction projected_potential(fem.gravityPotentialFes.get()); projected_potential.ProjectCoefficient(analytic_potential_coefficient); - const physics::GravitySolution solution = - physics::grav_potential_new(fem, args, density, displacement); + const physics::GravitySolution solution = physics::grav_potential_new(fem, args, density, displacement); - const int quadrature_order = get_gravity_quadrature_order(fem); - double local_solution_error_squared = 0.0; - double local_projection_error_squared = 0.0; + const int quadrature_order = get_gravity_quadrature_order(fem); + double local_solution_error_squared = 0.0; + double local_projection_error_squared = 0.0; double local_solution_projection_gap_squared = 0.0; double local_analytic_norm_squared = 0.0; double local_projected_norm_squared = 0.0; @@ -3260,112 +2550,73 @@ TEST_CASE( mfem::DenseMatrix mapping_jacobian(3); for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - fem.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element_id); if (transformation->Attribute == vacuum_attribute) { continue; } - const mfem::IntegrationRule &rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); - for (int quadrature_point_id = 0; - quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) { - const mfem::IntegrationPoint &point = - rule.IntPoint(quadrature_point_id); + for (int quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) { + const mfem::IntegrationPoint &point = rule.IntPoint(quadrature_point_id); transformation->SetIntPoint(&point); - fem.mapping->GetPhysicalPoint( - *transformation, point, physical_position - ); + fem.mapping->GetPhysicalPoint(*transformation, point, physical_position); fem.mapping->ComputeJacobian(*transformation, mapping_jacobian); const double mapping_determinant = mapping_jacobian.Det(); MFEM_VERIFY( - mapping_determinant > 0.0, - "Deformed potential test encountered a " - "non-positive mapping determinant." + mapping_determinant > 0.0, "Deformed potential test encountered a " + "non-positive mapping determinant." ); - const double expected_potential = - analytic_potential(physical_position); - const double computed_potential = - solution.phi.GetValue(element_id, point); - const double projected_potential_value = - projected_potential.GetValue(element_id, point); - const double weight = - point.weight * transformation->Weight() * mapping_determinant; + const double expected_potential = analytic_potential(physical_position); + const double computed_potential = solution.phi.GetValue(element_id, point); + const double projected_potential_value = projected_potential.GetValue(element_id, point); + const double weight = point.weight * transformation->Weight() * mapping_determinant; local_solution_error_squared += - weight * (computed_potential - expected_potential) * - (computed_potential - expected_potential); - local_projection_error_squared += - weight * (projected_potential_value - expected_potential) * - (projected_potential_value - expected_potential); - local_solution_projection_gap_squared += - weight * (computed_potential - projected_potential_value) * - (computed_potential - projected_potential_value); - local_analytic_norm_squared += - weight * expected_potential * expected_potential; - local_projected_norm_squared += - weight * projected_potential_value * projected_potential_value; + weight * (computed_potential - expected_potential) * (computed_potential - expected_potential); + local_projection_error_squared += weight * (projected_potential_value - expected_potential) * + (projected_potential_value - expected_potential); + local_solution_projection_gap_squared += weight * (computed_potential - projected_potential_value) * + (computed_potential - projected_potential_value); + local_analytic_norm_squared += weight * expected_potential * expected_potential; + local_projected_norm_squared += weight * projected_potential_value * projected_potential_value; local_maximum_relative_error = std::max( local_maximum_relative_error, std::abs(computed_potential - expected_potential) / - std::max( - std::abs(expected_potential), - std::numeric_limits::epsilon() - ) + std::max(std::abs(expected_potential), std::numeric_limits::epsilon()) ); } } const std::array local_values{ - local_solution_error_squared, local_projection_error_squared, - local_solution_projection_gap_squared, local_analytic_norm_squared, - local_projected_norm_squared + local_solution_error_squared, local_projection_error_squared, local_solution_projection_gap_squared, + local_analytic_norm_squared, local_projected_norm_squared }; std::array global_values{}; MPI_Allreduce( - local_values.data(), global_values.data(), - static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, + local_values.data(), global_values.data(), static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, fem.densityFes->GetComm() ); double maximum_relative_error = 0.0; MPI_Allreduce( - &local_maximum_relative_error, &maximum_relative_error, 1, MPI_DOUBLE, - MPI_MAX, fem.densityFes->GetComm() + &local_maximum_relative_error, &maximum_relative_error, 1, MPI_DOUBLE, MPI_MAX, fem.densityFes->GetComm() ); - const double solution_relative_error = - std::sqrt(global_values[0] / global_values[3]); - const double projection_relative_error = - std::sqrt(global_values[1] / global_values[3]); - const double solution_projection_gap = - std::sqrt(global_values[2] / global_values[4]); + const double solution_relative_error = std::sqrt(global_values[0] / global_values[3]); + const double projection_relative_error = std::sqrt(global_values[1] / global_values[3]); + const double solution_projection_gap = std::sqrt(global_values[2] / global_values[4]); INFO( - "Ellipsoid coefficients = (" << analytic.coefficient_x << ", " - << analytic.coefficient_y << ", " + "Ellipsoid coefficients = (" << analytic.coefficient_x << ", " << analytic.coefficient_y << ", " << analytic.coefficient_z << ")" ); - INFO( - "Analytic interior-potential L2 relative error = " - << solution_relative_error - ); - INFO( - "Analytic-potential FE projection L2 relative error = " - << projection_relative_error - ); - INFO( - "New-solver / analytic-potential projection relative gap = " - << solution_projection_gap - ); - INFO( - "Maximum interior pointwise relative potential error = " - << maximum_relative_error - ); + INFO("Analytic interior-potential L2 relative error = " << solution_relative_error); + INFO("Analytic-potential FE projection L2 relative error = " << projection_relative_error); + INFO("New-solver / analytic-potential projection relative gap = " << solution_projection_gap); + INFO("Maximum interior pointwise relative potential error = " << maximum_relative_error); REQUIRE(std::isfinite(solution_relative_error)); REQUIRE(std::isfinite(projection_relative_error)); @@ -3426,8 +2677,7 @@ static double compute_ferrers_n1_coefficient( const double length_scale = std::cbrt(axis_product); - auto integrand = [semi_axes, axis_product, length_scale, - first_denominator_axis, + auto integrand = [semi_axes, axis_product, length_scale, first_denominator_axis, second_denominator_axis](const double t) { if (t <= 0.0 || t >= 1.0) { return 0.0; @@ -3443,27 +2693,20 @@ static double compute_ferrers_n1_coefficient( const double u = length_scale * length_scale * s * s; - const double du_dt = - length_scale * length_scale * 2.0 * s / (one_minus_t * one_minus_t); + const double du_dt = length_scale * length_scale * 2.0 * s / (one_minus_t * one_minus_t); - const double delta = std::sqrt( - (semi_axes[0] * semi_axes[0] + u) * - (semi_axes[1] * semi_axes[1] + u) * - (semi_axes[2] * semi_axes[2] + u) + const double delta = std::sqrt( + (semi_axes[0] * semi_axes[0] + u) * (semi_axes[1] * semi_axes[1] + u) * (semi_axes[2] * semi_axes[2] + u) ); double value = axis_product * du_dt / delta; if (first_denominator_axis >= 0) { - value /= semi_axes[first_denominator_axis] * - semi_axes[first_denominator_axis] + - u; + value /= semi_axes[first_denominator_axis] * semi_axes[first_denominator_axis] + u; } if (second_denominator_axis >= 0) { - value /= semi_axes[second_denominator_axis] * - semi_axes[second_denominator_axis] + - u; + value /= semi_axes[second_denominator_axis] * semi_axes[second_denominator_axis] + u; } return value; @@ -3481,26 +2724,21 @@ static FerrersN1Analytic compute_ferrers_n1_analytic( const double semi_axis_y, const double semi_axis_z ) { - const std::array semi_axes{ - semi_axis_x, semi_axis_y, semi_axis_z - }; + const std::array semi_axes{semi_axis_x, semi_axis_y, semi_axis_z}; FerrersN1Analytic analytic{ - .potential_constant = compute_ferrers_n1_coefficient(semi_axes, -1, -1), - .first_coefficients = {}, + .potential_constant = compute_ferrers_n1_coefficient(semi_axes, -1, -1), + .first_coefficients = {}, .second_coefficients = {} }; for (int axis = 0; axis < 3; ++axis) { - analytic.first_coefficients[axis] = - compute_ferrers_n1_coefficient(semi_axes, axis, -1); + analytic.first_coefficients[axis] = compute_ferrers_n1_coefficient(semi_axes, axis, -1); } for (int first_axis = 0; first_axis < 3; ++first_axis) { for (int second_axis = first_axis; second_axis < 3; ++second_axis) { - const double coefficient = compute_ferrers_n1_coefficient( - semi_axes, first_axis, second_axis - ); + const double coefficient = compute_ferrers_n1_coefficient(semi_axes, first_axis, second_axis); analytic.second_coefficients[first_axis][second_axis] = coefficient; @@ -3517,8 +2755,7 @@ static double evaluate_ferrers_n1_potential( const FerrersN1Analytic &analytic ) { const std::array coordinate_squared{ - position(0) * position(0), position(1) * position(1), - position(2) * position(2) + position(0) * position(0), position(1) * position(1), position(2) * position(2) }; /* @@ -3530,14 +2767,11 @@ static double evaluate_ferrers_n1_potential( double potential_kernel = analytic.potential_constant; for (int first_axis = 0; first_axis < 3; ++first_axis) { - potential_kernel -= 2.0 * analytic.first_coefficients[first_axis] * - coordinate_squared[first_axis]; + potential_kernel -= 2.0 * analytic.first_coefficients[first_axis] * coordinate_squared[first_axis]; for (int second_axis = 0; second_axis < 3; ++second_axis) { - potential_kernel += - analytic.second_coefficients[first_axis][second_axis] * - coordinate_squared[first_axis] * - coordinate_squared[second_axis]; + potential_kernel += analytic.second_coefficients[first_axis][second_axis] * coordinate_squared[first_axis] * + coordinate_squared[second_axis]; } } @@ -3553,31 +2787,27 @@ static void evaluate_ferrers_n1_gradient( gradient.SetSize(3); const std::array coordinate_squared{ - position(0) * position(0), position(1) * position(1), - position(2) * position(2) + position(0) * position(0), position(1) * position(1), position(2) * position(2) }; for (int axis = 0; axis < 3; ++axis) { double coefficient = analytic.first_coefficients[axis]; for (int other_axis = 0; other_axis < 3; ++other_axis) { - coefficient -= analytic.second_coefficients[axis][other_axis] * - coordinate_squared[other_axis]; + coefficient -= analytic.second_coefficients[axis][other_axis] * coordinate_squared[other_axis]; } /* * The solver stores grad(Phi), which points outward for a * negative gravitational potential. */ - gradient(axis) = 2.0 * M_PI * utils::G * central_density * - position(axis) * coefficient; + gradient(axis) = 2.0 * M_PI * utils::G * central_density * position(axis) * coefficient; } } TEST_CASE( "New Gravity Potential Matches Analytic Ferrers Ellipsoid", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration &tags::accuracy + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::accuracy ) { auto args = test_utils::setup_args(); @@ -3602,12 +2832,9 @@ TEST_CASE( const double semi_axis_y = y_scale * radius; const double semi_axis_z = z_scale * radius; - REQUIRE_THAT( - x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14) - ); + REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14)); - auto displacement_function = [](const mfem::Vector &position, - mfem::Vector &value) { + auto displacement_function = [](const mfem::Vector &position, mfem::Vector &value) { value.SetSize(3); value(0) = (x_scale - 1.0) * position(0); @@ -3615,9 +2842,7 @@ TEST_CASE( value(2) = (z_scale - 1.0) * position(2); }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, displacement_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function); mfem::ParGridFunction displacement(fem.displacementFes.get()); @@ -3627,35 +2852,26 @@ TEST_CASE( physics::update_stiffness_matrix(fem); - const int vacuum_attribute = - fem.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = fem.domainMapperStateless->GetVacuumElementAttribute(); /* * For rho = rho_c (1 - m^2), the exact mass is * * M = 8 pi a b c rho_c / 15. */ - const double central_density = - 15.0 * mass / (8.0 * M_PI * semi_axis_x * semi_axis_y * semi_axis_z); + const double central_density = 15.0 * mass / (8.0 * M_PI * semi_axis_x * semi_axis_y * semi_axis_z); - auto density_function = [central_density, semi_axis_x, semi_axis_y, - semi_axis_z](const mfem::Vector &position) { - const double ellipsoidal_radius_squared = - position(0) * position(0) / (semi_axis_x * semi_axis_x) + - position(1) * position(1) / (semi_axis_y * semi_axis_y) + - position(2) * position(2) / (semi_axis_z * semi_axis_z); + auto density_function = [central_density, semi_axis_x, semi_axis_y, semi_axis_z](const mfem::Vector &position) { + const double ellipsoidal_radius_squared = position(0) * position(0) / (semi_axis_x * semi_axis_x) + + position(1) * position(1) / (semi_axis_y * semi_axis_y) + + position(2) * position(2) / (semi_axis_z * semi_axis_z); - return central_density * - std::max(0.0, 1.0 - ellipsoidal_radius_squared); + return central_density * std::max(0.0, 1.0 - ellipsoidal_radius_squared); }; - mapping::PhysicalPositionFunctionCoefficient physical_density_coefficient( - *fem.mapping, density_function - ); + mapping::PhysicalPositionFunctionCoefficient physical_density_coefficient(*fem.mapping, density_function); - FerrersVacuumMaskedCoefficient stellar_density_coefficient( - physical_density_coefficient, vacuum_attribute - ); + FerrersVacuumMaskedCoefficient stellar_density_coefficient(physical_density_coefficient, vacuum_attribute); mfem::GridFunction density(fem.densityFes.get()); @@ -3663,9 +2879,7 @@ TEST_CASE( density.ProjectCoefficient(stellar_density_coefficient); - const double projected_mass = analysis::domain_integrate_grid_function( - fem, density, utils::DOMAINS::STELLAR - ); + const double projected_mass = analysis::domain_integrate_grid_function(fem, density, utils::DOMAINS::STELLAR); REQUIRE(std::isfinite(projected_mass)); REQUIRE(projected_mass > 0.0); @@ -3683,15 +2897,13 @@ TEST_CASE( fem.com = analysis::get_com(fem, density); - fem.Q = physics::compute_quadrupole_moment_tensor(fem, density, fem.com); + fem.Q = physics::compute_quadrupole_moment_tensor(fem, density, fem.com); - const double normalized_quadrupole = - fem.Q.FNorm() / (mass * radius * radius); + const double normalized_quadrupole = fem.Q.FNorm() / (mass * radius * radius); // REQUIRE(normalized_quadrupole > 1.0e-3); - const FerrersN1Analytic analytic = - compute_ferrers_n1_analytic(semi_axis_x, semi_axis_y, semi_axis_z); + const FerrersN1Analytic analytic = compute_ferrers_n1_analytic(semi_axis_x, semi_axis_y, semi_axis_z); /* * Independent analytic consistency checks. @@ -3699,45 +2911,32 @@ TEST_CASE( * Sum(A_i) = 2 supplies the constant part of Poisson's * equation. The B_ij identities supply the -m^2 part. */ - const double first_coefficient_sum = analytic.first_coefficients[0] + - analytic.first_coefficients[1] + - analytic.first_coefficients[2]; + const double first_coefficient_sum = + analytic.first_coefficients[0] + analytic.first_coefficients[1] + analytic.first_coefficients[2]; - REQUIRE_THAT( - first_coefficient_sum, Catch::Matchers::WithinAbs(2.0, 1.0e-11) - ); + REQUIRE_THAT(first_coefficient_sum, Catch::Matchers::WithinAbs(2.0, 1.0e-11)); const std::array semi_axes_squared{ - semi_axis_x * semi_axis_x, semi_axis_y * semi_axis_y, - semi_axis_z * semi_axis_z + semi_axis_x * semi_axis_x, semi_axis_y * semi_axis_y, semi_axis_z * semi_axis_z }; for (int axis = 0; axis < 3; ++axis) { - double poisson_coefficient = - 3.0 * analytic.second_coefficients[axis][axis]; + double poisson_coefficient = 3.0 * analytic.second_coefficients[axis][axis]; for (int other_axis = 0; other_axis < 3; ++other_axis) { if (other_axis != axis) { - poisson_coefficient += - analytic.second_coefficients[axis][other_axis]; + poisson_coefficient += analytic.second_coefficients[axis][other_axis]; } } - REQUIRE_THAT( - poisson_coefficient, - Catch::Matchers::WithinRel(2.0 / semi_axes_squared[axis], 1.0e-10) - ); + REQUIRE_THAT(poisson_coefficient, Catch::Matchers::WithinRel(2.0 / semi_axes_squared[axis], 1.0e-10)); } - const physics::GravitySolution solution = - physics::grav_potential_new(fem, args, density, displacement); + const physics::GravitySolution solution = physics::grav_potential_new(fem, args, density, displacement); - auto analytic_potential_function = - [represented_central_density, analytic](const mfem::Vector &position) { - return evaluate_ferrers_n1_potential( - position, represented_central_density, analytic - ); - }; + auto analytic_potential_function = [represented_central_density, analytic](const mfem::Vector &position) { + return evaluate_ferrers_n1_potential(position, represented_central_density, analytic); + }; mapping::PhysicalPositionFunctionCoefficient physical_potential_coefficient( *fem.mapping, analytic_potential_function @@ -3748,9 +2947,7 @@ TEST_CASE( * attribute overload. It also prevents evaluation of the quartic * interior formula in the compactified vacuum. */ - FerrersVacuumMaskedCoefficient stellar_potential_coefficient( - physical_potential_coefficient, vacuum_attribute - ); + FerrersVacuumMaskedCoefficient stellar_potential_coefficient(physical_potential_coefficient, vacuum_attribute); mfem::ParGridFunction projected_potential(fem.gravityPotentialFes.get()); @@ -3758,7 +2955,7 @@ TEST_CASE( projected_potential.ProjectCoefficient(stellar_potential_coefficient); - const int quadrature_order = get_gravity_quadrature_order(fem) + 4; + const int quadrature_order = get_gravity_quadrature_order(fem) + 4; double local_potential_error_squared = 0.0; double local_projection_error_squared = 0.0; @@ -3778,27 +2975,20 @@ TEST_CASE( mfem::DenseMatrix mapping_jacobian(3, 3); for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - fem.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element_id); if (transformation->Attribute == vacuum_attribute) { continue; } - const mfem::IntegrationRule &rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); - for (int quadrature_point_id = 0; - quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) { - const mfem::IntegrationPoint &point = - rule.IntPoint(quadrature_point_id); + for (int quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) { + const mfem::IntegrationPoint &point = rule.IntPoint(quadrature_point_id); transformation->SetIntPoint(&point); - fem.mapping->GetPhysicalPoint( - *transformation, point, physical_position - ); + fem.mapping->GetPhysicalPoint(*transformation, point, physical_position); fem.mapping->ComputeJacobian(*transformation, mapping_jacobian); @@ -3809,20 +2999,14 @@ TEST_CASE( "Ferrers test encountered an invalid mapping determinant." ); - const double expected_potential = evaluate_ferrers_n1_potential( - physical_position, represented_central_density, analytic - ); + const double expected_potential = + evaluate_ferrers_n1_potential(physical_position, represented_central_density, analytic); - evaluate_ferrers_n1_gradient( - physical_position, represented_central_density, analytic, - analytic_field - ); + evaluate_ferrers_n1_gradient(physical_position, represented_central_density, analytic, analytic_field); - const double computed_potential = - solution.phi.GetValue(element_id, point); + const double computed_potential = solution.phi.GetValue(element_id, point); - const double projected_potential_value = - projected_potential.GetValue(element_id, point); + const double projected_potential_value = projected_potential.GetValue(element_id, point); solution.gradPhi.GetVectorValue(element_id, point, reference_field); @@ -3834,91 +3018,67 @@ TEST_CASE( field_difference -= analytic_field; - const double weight = - point.weight * transformation->Weight() * mapping_determinant; + const double weight = point.weight * transformation->Weight() * mapping_determinant; - const double potential_error = - computed_potential - expected_potential; + const double potential_error = computed_potential - expected_potential; - const double projection_error = - projected_potential_value - expected_potential; + const double projection_error = projected_potential_value - expected_potential; - const double solution_projection_difference = - computed_potential - projected_potential_value; + const double solution_projection_difference = computed_potential - projected_potential_value; - local_potential_error_squared += - weight * potential_error * potential_error; + local_potential_error_squared += weight * potential_error * potential_error; - local_projection_error_squared += - weight * projection_error * projection_error; + local_projection_error_squared += weight * projection_error * projection_error; local_solution_projection_gap_squared += - weight * solution_projection_difference * - solution_projection_difference; + weight * solution_projection_difference * solution_projection_difference; - local_potential_norm_squared += - weight * expected_potential * expected_potential; + local_potential_norm_squared += weight * expected_potential * expected_potential; - local_projection_norm_squared += - weight * projected_potential_value * projected_potential_value; + local_projection_norm_squared += weight * projected_potential_value * projected_potential_value; - local_field_error_squared += - weight * (field_difference * field_difference); + local_field_error_squared += weight * (field_difference * field_difference); - local_field_norm_squared += - weight * (analytic_field * analytic_field); + local_field_norm_squared += weight * (analytic_field * analytic_field); local_maximum_potential_error = std::max( local_maximum_potential_error, std::abs(potential_error) / - std::max( - std::abs(expected_potential), - std::numeric_limits::epsilon() - ) + std::max(std::abs(expected_potential), std::numeric_limits::epsilon()) ); } } const std::array local_values{ - local_potential_error_squared, - local_projection_error_squared, - local_solution_projection_gap_squared, - local_potential_norm_squared, - local_projection_norm_squared, - local_field_error_squared, + local_potential_error_squared, local_projection_error_squared, local_solution_projection_gap_squared, + local_potential_norm_squared, local_projection_norm_squared, local_field_error_squared, local_field_norm_squared }; std::array global_values{}; MPI_Allreduce( - local_values.data(), global_values.data(), - static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, + local_values.data(), global_values.data(), static_cast(local_values.size()), MPI_DOUBLE, MPI_SUM, fem.densityFes->GetComm() ); double maximum_potential_error = 0.0; MPI_Allreduce( - &local_maximum_potential_error, &maximum_potential_error, 1, MPI_DOUBLE, - MPI_MAX, fem.densityFes->GetComm() + &local_maximum_potential_error, &maximum_potential_error, 1, MPI_DOUBLE, MPI_MAX, fem.densityFes->GetComm() ); REQUIRE(global_values[3] > 0.0); REQUIRE(global_values[4] > 0.0); REQUIRE(global_values[6] > 0.0); - const double potential_relative_error = - std::sqrt(global_values[0] / global_values[3]); + const double potential_relative_error = std::sqrt(global_values[0] / global_values[3]); - const double projection_relative_error = - std::sqrt(global_values[1] / global_values[3]); + const double projection_relative_error = std::sqrt(global_values[1] / global_values[3]); - const double solution_projection_gap = - std::sqrt(global_values[2] / global_values[4]); + const double solution_projection_gap = std::sqrt(global_values[2] / global_values[4]); - const double field_relative_error = - std::sqrt(global_values[5] / global_values[6]); + const double field_relative_error = std::sqrt(global_values[5] / global_values[6]); INFO("Projected mass before normalization = " << projected_mass); @@ -3928,22 +3088,13 @@ TEST_CASE( INFO("Ferrers potential L2 relative error = " << potential_relative_error); - INFO( - "Ferrers potential FE-projection relative error = " - << projection_relative_error - ); + INFO("Ferrers potential FE-projection relative error = " << projection_relative_error); - INFO( - "New-solver / Ferrers-potential projection gap = " - << solution_projection_gap - ); + INFO("New-solver / Ferrers-potential projection gap = " << solution_projection_gap); INFO("Ferrers field L2 relative error = " << field_relative_error); - INFO( - "Maximum interior pointwise potential relative error = " - << maximum_potential_error - ); + INFO("Maximum interior pointwise potential relative error = " << maximum_potential_error); REQUIRE(std::isfinite(potential_relative_error)); REQUIRE(std::isfinite(projection_relative_error)); diff --git a/tests/physics/gravity_monopole_accuracy.cpp b/tests/physics/gravity_monopole_accuracy.cpp index 2e41d4a..68635de 100644 --- a/tests/physics/gravity_monopole_accuracy.cpp +++ b/tests/physics/gravity_monopole_accuracy.cpp @@ -14,8 +14,7 @@ import mean_field; import test_helpers; namespace { - constexpr std::array shell_boundaries{0.0, 0.25, 0.50, - 0.75, 0.90, 1.0}; + constexpr std::array shell_boundaries{0.0, 0.25, 0.50, 0.75, 0.90, 1.0}; constexpr int shell_count = static_cast(shell_boundaries.size()) - 1; enum class MappingPath { legacy, stateless }; @@ -49,24 +48,17 @@ namespace { struct GravitationalEnergies { double binding{0.0}; double virial{0.0}; - double minimum_mapping_determinant{ - std::numeric_limits::infinity() - }; - double maximum_mapping_determinant{ - -std::numeric_limits::infinity() - }; + double minimum_mapping_determinant{std::numeric_limits::infinity()}; + double maximum_mapping_determinant{-std::numeric_limits::infinity()}; long long invalid_points{0}; std::array mapping_status_counts{}; int first_invalid_element{-1}; int first_invalid_attribute{-1}; int first_invalid_quadrature_point{-1}; - double first_invalid_determinant{ - std::numeric_limits::quiet_NaN() - }; + double first_invalid_determinant{std::numeric_limits::quiet_NaN()}; }; - constexpr int - mapping_status_index(const mean_field::mapping::MappingStatus status) { + constexpr int mapping_status_index(const mean_field::mapping::MappingStatus status) { return static_cast(status); } @@ -85,10 +77,7 @@ namespace { ) { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -99,9 +88,7 @@ namespace { ) { const double local_dot = lhs * rhs; double result = 0.0; - MPI_Allreduce( - &local_dot, &result, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_dot, &result, 1, MPI_DOUBLE, MPI_SUM, communicator); return result; } @@ -116,15 +103,11 @@ namespace { difference -= reference; return global_norm(difference, communicator) / - std::max( - global_norm(reference, communicator), - std::numeric_limits::epsilon() - ); + std::max(global_norm(reference, communicator), std::numeric_limits::epsilon()); } int get_shell(const double coordinate) { - const double clamped = - std::clamp(coordinate, 0.0, std::nextafter(1.0, 0.0)); + const double clamped = std::clamp(coordinate, 0.0, std::nextafter(1.0, 0.0)); for (int shell = 0; shell < shell_count; ++shell) { if (clamped < shell_boundaries[shell + 1]) { @@ -135,16 +118,11 @@ namespace { return shell_count - 1; } - bool - retryable_infinity_status(const mean_field::mapping::MappingStatus status) { - return status == mean_field::mapping::MappingStatus:: - at_compactified_infinity || - status == mean_field::mapping::MappingStatus:: - outside_reference_domain || - status == - mean_field::mapping::MappingStatus::non_finite_result || - status == - mean_field::mapping::MappingStatus::non_positive_determinant; + bool retryable_infinity_status(const mean_field::mapping::MappingStatus status) { + return status == mean_field::mapping::MappingStatus::at_compactified_infinity || + status == mean_field::mapping::MappingStatus::outside_reference_domain || + status == mean_field::mapping::MappingStatus::non_finite_result || + status == mean_field::mapping::MappingStatus::non_positive_determinant; } class ProjectionGeometry { @@ -169,118 +147,80 @@ namespace { m_used_infinity_limit = false; const int element_id = transformation.ElementNo; - MFEM_VERIFY( - element_id >= 0, - "Projection coefficient received an invalid element number." - ); + MFEM_VERIFY(element_id >= 0, "Projection coefficient received an invalid element number."); - const mfem::FiniteElement &displacement_element = - *m_fem.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *m_fem.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *m_fem.compactificationFes->GetFE(element_id); mfem::Array displacement_dofs; mfem::Array compactification_dofs; mfem::DofTransformation *displacement_transform = - m_fem.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + m_fem.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_transform = - m_fem.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + m_fem.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; - m_displacement.GetSubVector( - displacement_dofs, element_displacement - ); - m_fem.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + m_displacement.GetSubVector(displacement_dofs, element_displacement); + m_fem.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_transform != nullptr) { - displacement_transform->InvTransformPrimal( - element_displacement - ); + displacement_transform->InvTransformPrimal(element_displacement); } if (compactification_transform != nullptr) { - compactification_transform->InvTransformPrimal( - element_compactification - ); + compactification_transform->InvTransformPrimal(element_compactification); } - const mean_field::mapping::ElementDisplacementData - displacement_data = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); - - const mean_field::mapping::ElementCompactificationData - compactification_data( - compactification_element, element_compactification + const mean_field::mapping::ElementDisplacementData displacement_data = + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacement_element, element_displacement ); + const mean_field::mapping::ElementCompactificationData compactification_data( + compactification_element, element_compactification + ); + const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - mfem::Vector compactification_shape( - compactification_element.GetDof() - ); - compactification_element.CalcShape( - integration_point, compactification_shape - ); + mfem::Vector compactification_shape(compactification_element.GetDof()); + compactification_element.CalcShape(integration_point, compactification_shape); - const double coordinate = - element_compactification * compactification_shape; + const double coordinate = element_compactification * compactification_shape; - mean_field::mapping::MappingStatus status = m_mapper.EvaluatePoint( - mapping_data, transformation, integration_point, m_workspace, - context - ); + mean_field::mapping::MappingStatus status = + m_mapper.EvaluatePoint(mapping_data, transformation, integration_point, m_workspace, context); if (status == mean_field::mapping::MappingStatus::valid) { transformation.SetIntPoint(&integration_point); return status; } - const bool infinity_request = - m_mapper.IsCompactifiedElement(transformation) && - coordinate >= 1.0 - 1.0e-10; + const bool infinity_request = m_mapper.IsCompactifiedElement(transformation) && coordinate >= 1.0 - 1.0e-10; - if (!permit_infinity_limit || !infinity_request || - !retryable_infinity_status(status)) { + if (!permit_infinity_limit || !infinity_request || !retryable_infinity_status(status)) { transformation.SetIntPoint(&integration_point); return status; } - const mfem::IntegrationPoint ¢er = - mfem::Geometries.GetCenter(transformation.GetGeometryType()); + const mfem::IntegrationPoint ¢er = mfem::Geometries.GetCenter(transformation.GetGeometryType()); - constexpr std::array inward_fractions{ - 1.0e-12, 1.0e-11, 1.0e-10, 1.0e-9, 1.0e-8, 1.0e-7, - 1.0e-6, 1.0e-5, 1.0e-4, 1.0e-3, 1.0e-2 - }; + constexpr std::array inward_fractions{1.0e-12, 1.0e-11, 1.0e-10, 1.0e-9, 1.0e-8, 1.0e-7, + 1.0e-6, 1.0e-5, 1.0e-4, 1.0e-3, 1.0e-2}; for (const double fraction : inward_fractions) { mfem::IntegrationPoint inward; - inward.x = (1.0 - fraction) * integration_point.x + - fraction * center.x; - inward.y = (1.0 - fraction) * integration_point.y + - fraction * center.y; - inward.z = (1.0 - fraction) * integration_point.z + - fraction * center.z; + inward.x = (1.0 - fraction) * integration_point.x + fraction * center.x; + inward.y = (1.0 - fraction) * integration_point.y + fraction * center.y; + inward.z = (1.0 - fraction) * integration_point.z + fraction * center.z; inward.weight = integration_point.weight; - status = m_mapper.EvaluatePoint( - mapping_data, transformation, inward, m_workspace, context - ); + status = m_mapper.EvaluatePoint(mapping_data, transformation, inward, m_workspace, context); if (status == mean_field::mapping::MappingStatus::valid) { m_used_infinity_limit = true; @@ -335,15 +275,12 @@ namespace { mean_field::mapping::MappingPointContext context; const mean_field::mapping::MappingStatus status = - m_geometry.Evaluate( - transformation, integration_point, context, true - ); + m_geometry.Evaluate(transformation, integration_point, context, true); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Stateless monopole-potential projection failed with status " - << static_cast(status) << " on element " - << transformation.ElementNo << '.' + << static_cast(status) << " on element " << transformation.ElementNo << '.' ); if (m_geometry.UsedInfinityLimit()) { @@ -352,17 +289,13 @@ namespace { const double radius = context.physical_position.Norml2(); - MFEM_VERIFY( - std::isfinite(radius) && radius > 0.0, - "Invalid monopole projection radius." - ); + MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid monopole projection radius."); if (transformation.Attribute == m_vacuum_attribute) { return -mean_field::utils::G * m_mass / radius; } - return -mean_field::utils::G * m_mass * - (3.0 * m_radius * m_radius - radius * radius) / + return -mean_field::utils::G * m_mass * (3.0 * m_radius * m_radius - radius * radius) / (2.0 * m_radius * m_radius * m_radius); } @@ -399,75 +332,53 @@ namespace { mfem::Vector local_rhs(f.gravityFluxFes->GetVSize()); local_rhs = 0.0; - mean_field::mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); - const int quadrature_order = - 2 * f.gravityFluxFes->GetMaxElementOrder() + 8; + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); + const int quadrature_order = 2 * f.gravityFluxFes->GetMaxElementOrder() + 8; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - const mfem::FiniteElement &gravity_element = - *f.gravityFluxFes->GetFE(element_id); - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &gravity_element = *f.gravityFluxFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); mfem::Array gravity_dofs; mfem::Array displacement_dofs; mfem::Array compactification_dofs; - mfem::DofTransformation *gravity_transform = - f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs); + mfem::DofTransformation *gravity_transform = f.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs); mfem::DofTransformation *displacement_transform = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_transform = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; displacement.GetSubVector(displacement_dofs, element_displacement); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_transform != nullptr) { - displacement_transform->InvTransformPrimal( - element_displacement - ); + displacement_transform->InvTransformPrimal(element_displacement); } if (compactification_transform != nullptr) { - compactification_transform->InvTransformPrimal( - element_compactification - ); + compactification_transform->InvTransformPrimal(element_compactification); } - const mean_field::mapping::ElementDisplacementData - displacement_data = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); - - const mean_field::mapping::ElementCompactificationData - compactification_data( - compactification_element, element_compactification + const mean_field::mapping::ElementDisplacementData displacement_data = + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacement_element, element_displacement ); + const mean_field::mapping::ElementCompactificationData compactification_data( + compactification_element, element_compactification + ); + const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; const int dof_count = gravity_element.GetDof(); @@ -480,9 +391,7 @@ namespace { element_rhs = 0.0; - const mfem::IntegrationRule &rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &point = rule.IntPoint(q); @@ -490,38 +399,27 @@ namespace { mean_field::mapping::VolumeMappingContext context; const mean_field::mapping::MappingStatus status = - f.domainMapperStateless->EvaluateVolume( - mapping_data, *transformation, point, workspace, context - ); + f.domainMapperStateless->EvaluateVolume(mapping_data, *transformation, point, workspace, context); MFEM_VERIFY( status == mean_field::mapping::MappingStatus::valid, "Mapped monopole projection RHS failed with status " - << static_cast(status) << " on element " - << element_id << ", quadrature point " << q << '.' + << static_cast(status) << " on element " << element_id << ", quadrature point " << q << '.' ); analytic_field = context.mapping.physical_position; const double radius = analytic_field.Norml2(); - MFEM_VERIFY( - std::isfinite(radius) && radius > 0.0, - "Invalid physical radius in projection RHS." - ); + MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid physical radius in projection RHS."); if (transformation->Attribute == vacuum_attribute) { - analytic_field *= mean_field::utils::G * mass / - (radius * radius * radius); + analytic_field *= mean_field::utils::G * mass / (radius * radius * radius); } else { - analytic_field *= - mean_field::utils::G * mass / - (stellar_radius * stellar_radius * stellar_radius); + analytic_field *= mean_field::utils::G * mass / (stellar_radius * stellar_radius * stellar_radius); } - context.mapping.mapping_jacobian.MultTranspose( - analytic_field, pulled_rhs_field - ); + context.mapping.mapping_jacobian.MultTranspose(analytic_field, pulled_rhs_field); transformation->SetIntPoint(&point); gravity_element.CalcVShape(*transformation, vector_shape); @@ -529,10 +427,8 @@ namespace { const double weight = point.weight * transformation->Weight(); for (int i = 0; i < dof_count; ++i) { - for (int component = 0; component < dimension; - ++component) { - element_rhs(i) += weight * vector_shape(i, component) * - pulled_rhs_field(component); + for (int component = 0; component < dimension; ++component) { + element_rhs(i) += weight * vector_shape(i, component) * pulled_rhs_field(component); } } } @@ -559,13 +455,9 @@ namespace { mfem::Vector displacement_true; displacement.GetTrueDofs(displacement_true); - const mfem::Vector projection_rhs = assemble_monopole_projection_rhs( - f, displacement, mass, stellar_radius - ); + const mfem::Vector projection_rhs = assemble_monopole_projection_rhs(f, displacement, mass, stellar_radius); - mean_field::operators::PreparedMappedHDivMassOperator mass_operator( - f, *f.domainMapperStateless - ); + mean_field::operators::PreparedMappedHDivMassOperator mass_operator(f, *f.domainMapperStateless); mass_operator.Prepare(displacement_true); mfem::Vector projected_gradient(f.gravityFluxFes->GetTrueVSize()); @@ -583,30 +475,15 @@ namespace { mass_operator.Mult(projected_gradient, projection_residual); projection_residual -= projection_rhs; - const double source_norm = - global_norm(projection_rhs, f.gravityFluxFes->GetComm()); - const double residual_norm = - global_norm(projection_residual, f.gravityFluxFes->GetComm()); - const double relative_residual = - residual_norm / - std::max(source_norm, std::numeric_limits::epsilon()); + const double source_norm = global_norm(projection_rhs, f.gravityFluxFes->GetComm()); + const double residual_norm = global_norm(projection_residual, f.gravityFluxFes->GetComm()); + const double relative_residual = residual_norm / std::max(source_norm, std::numeric_limits::epsilon()); INFO("Mapped H(div) projection converged = " << solver.GetConverged()); - INFO( - "Mapped H(div) projection iterations = " - << solver.GetNumIterations() - ); - INFO( - "Mapped H(div) projection reported final norm = " - << solver.GetFinalNorm() - ); - INFO( - "Mapped H(div) projection direct residual norm = " << residual_norm - ); - INFO( - "Mapped H(div) projection direct relative residual = " - << relative_residual - ); + INFO("Mapped H(div) projection iterations = " << solver.GetNumIterations()); + INFO("Mapped H(div) projection reported final norm = " << solver.GetFinalNorm()); + INFO("Mapped H(div) projection direct residual norm = " << residual_norm); + INFO("Mapped H(div) projection direct relative residual = " << relative_residual); REQUIRE(std::isfinite(relative_residual)); REQUIRE(relative_residual < 1.0e-8); @@ -624,9 +501,7 @@ namespace { mfem::Vector displacement_true; displacement.GetTrueDofs(displacement_true); - mean_field::operators::PreparedMappedHDivMassOperator mass_operator( - f, *f.domainMapperStateless - ); + mean_field::operators::PreparedMappedHDivMassOperator mass_operator(f, *f.domainMapperStateless); mass_operator.Prepare(displacement_true); mfem::Vector difference(computed); @@ -638,12 +513,10 @@ namespace { mass_operator.Mult(difference, difference_action); mass_operator.Mult(reference, reference_action); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); - const double difference_energy = - global_dot(difference, difference_action, communicator); - const double reference_energy = - global_dot(reference, reference_action, communicator); + const double difference_energy = global_dot(difference, difference_action, communicator); + const double reference_energy = global_dot(reference, reference_action, communicator); REQUIRE(difference_energy >= -1.0e-12 * std::abs(reference_energy)); REQUIRE(reference_energy > 0.0); @@ -661,116 +534,82 @@ namespace { std::array local{}; long long local_invalid_points = 0; - mean_field::mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); const int quadrature_order = - 2 * std::max( - f.gravityPotentialFes->GetMaxElementOrder(), - f.gravityFluxFes->GetMaxElementOrder() - ) + - 8; + 2 * std::max(f.gravityPotentialFes->GetMaxElementOrder(), f.gravityFluxFes->GetMaxElementOrder()) + 8; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); if (transformation->Attribute != vacuum_attribute) { continue; } - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); mfem::Array displacement_dofs; mfem::Array compactification_dofs; mfem::DofTransformation *displacement_transform = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_transform = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; displacement.GetSubVector(displacement_dofs, element_displacement); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_transform != nullptr) { - displacement_transform->InvTransformPrimal( - element_displacement - ); + displacement_transform->InvTransformPrimal(element_displacement); } if (compactification_transform != nullptr) { - compactification_transform->InvTransformPrimal( - element_compactification - ); + compactification_transform->InvTransformPrimal(element_compactification); } - const mean_field::mapping::ElementDisplacementData - displacement_data = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); - - const mean_field::mapping::ElementCompactificationData - compactification_data( - compactification_element, element_compactification + const mean_field::mapping::ElementDisplacementData displacement_data = + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacement_element, element_displacement ); + const mean_field::mapping::ElementCompactificationData compactification_data( + compactification_element, element_compactification + ); + const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - mfem::Vector compactification_shape( - compactification_element.GetDof() - ); + mfem::Vector compactification_shape(compactification_element.GetDof()); - const mfem::IntegrationRule &rule = mfem::IntRules.Get( - transformation->GetGeometryType(), quadrature_order - ); + const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), quadrature_order); for (int q = 0; q < rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &point = rule.IntPoint(q); transformation->SetIntPoint(&point); - compactification_element.CalcShape( - point, compactification_shape - ); + compactification_element.CalcShape(point, compactification_shape); - const double coordinate = - element_compactification * compactification_shape; - const int shell = get_shell(coordinate); + const double coordinate = element_compactification * compactification_shape; + const int shell = get_shell(coordinate); mfem::Vector reference_field(3); mfem::Vector physical_field(3); mfem::Vector physical_position(3); - solution.gradPhi.GetVectorValue( - element_id, point, reference_field - ); + solution.gradPhi.GetVectorValue(element_id, point, reference_field); if (mapping_path == MappingPath::stateless) { mean_field::mapping::VolumeMappingContext context; - const mean_field::mapping::MappingStatus status = - f.domainMapperStateless->EvaluateVolume( - mapping_data, *transformation, point, workspace, - context - ); + const mean_field::mapping::MappingStatus status = f.domainMapperStateless->EvaluateVolume( + mapping_data, *transformation, point, workspace, context + ); if (status != mean_field::mapping::MappingStatus::valid) { ++local_invalid_points; @@ -778,13 +617,9 @@ namespace { } physical_position = context.mapping.physical_position; - mean_field::mapping::MapHDivFluxToPhysical( - context.mapping, reference_field, physical_field - ); + mean_field::mapping::MapHDivFluxToPhysical(context.mapping, reference_field, physical_field); } else { - f.mapping->GetPhysicalPoint( - *transformation, point, physical_position - ); + f.mapping->GetPhysicalPoint(*transformation, point, physical_position); mfem::DenseMatrix jacobian(3); f.mapping->ComputeJacobian(*transformation, jacobian); @@ -815,45 +650,32 @@ namespace { mfem::Vector tangential_field(physical_field); tangential_field.Add(-radial_field, radial_unit); - const double potential = - solution.phi.GetValue(element_id, point); + const double potential = solution.phi.GetValue(element_id, point); - const double scaled_potential = - -radius * potential / (mean_field::utils::G * mass); - const double scaled_radial_field = - radius * radius * radial_field / - (mean_field::utils::G * mass); + const double scaled_potential = -radius * potential / (mean_field::utils::G * mass); + const double scaled_radial_field = radius * radius * radial_field / (mean_field::utils::G * mass); const double scaled_tangential_field = - radius * radius * tangential_field.Norml2() / - (mean_field::utils::G * mass); + radius * radius * tangential_field.Norml2() / (mean_field::utils::G * mass); - if (!std::isfinite(scaled_potential) || - !std::isfinite(scaled_radial_field) || + if (!std::isfinite(scaled_potential) || !std::isfinite(scaled_radial_field) || !std::isfinite(scaled_tangential_field)) { ++local_invalid_points; continue; } - const double weight = point.weight * transformation->Weight(); + const double weight = point.weight * transformation->Weight(); ShellAccumulator &accumulator = local[shell]; ++accumulator.points; accumulator.weight += weight; - accumulator.minimum_radius = - std::min(accumulator.minimum_radius, radius); - accumulator.maximum_radius = - std::max(accumulator.maximum_radius, radius); + accumulator.minimum_radius = std::min(accumulator.minimum_radius, radius); + accumulator.maximum_radius = std::max(accumulator.maximum_radius, radius); - accumulator.potential_error_squared += - weight * (scaled_potential - 1.0) * - (scaled_potential - 1.0); - accumulator.radial_error_squared += - weight * (scaled_radial_field - 1.0) * - (scaled_radial_field - 1.0); - accumulator.tangential_squared += - weight * scaled_tangential_field * scaled_tangential_field; + accumulator.potential_error_squared += weight * (scaled_potential - 1.0) * (scaled_potential - 1.0); + accumulator.radial_error_squared += weight * (scaled_radial_field - 1.0) * (scaled_radial_field - 1.0); + accumulator.tangential_squared += weight * scaled_tangential_field * scaled_tangential_field; } } @@ -861,56 +683,37 @@ namespace { ShellMeasurement measurement; - MPI_Allreduce( - &local_invalid_points, &measurement.invalid_points, 1, - MPI_LONG_LONG, MPI_SUM, communicator - ); + MPI_Allreduce(&local_invalid_points, &measurement.invalid_points, 1, MPI_LONG_LONG, MPI_SUM, communicator); for (int shell = 0; shell < shell_count; ++shell) { long long points = 0; - MPI_Allreduce( - &local[shell].points, &points, 1, MPI_LONG_LONG, MPI_SUM, - communicator - ); + MPI_Allreduce(&local[shell].points, &points, 1, MPI_LONG_LONG, MPI_SUM, communicator); const double local_sums[4]{ - local[shell].weight, local[shell].potential_error_squared, - local[shell].radial_error_squared, + local[shell].weight, local[shell].potential_error_squared, local[shell].radial_error_squared, local[shell].tangential_squared }; double sums[4]{}; - MPI_Allreduce( - local_sums, sums, 4, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(local_sums, sums, 4, MPI_DOUBLE, MPI_SUM, communicator); double minimum_radius = 0.0; double maximum_radius = 0.0; - MPI_Allreduce( - &local[shell].minimum_radius, &minimum_radius, 1, MPI_DOUBLE, - MPI_MIN, communicator - ); - MPI_Allreduce( - &local[shell].maximum_radius, &maximum_radius, 1, MPI_DOUBLE, - MPI_MAX, communicator - ); + MPI_Allreduce(&local[shell].minimum_radius, &minimum_radius, 1, MPI_DOUBLE, MPI_MIN, communicator); + MPI_Allreduce(&local[shell].maximum_radius, &maximum_radius, 1, MPI_DOUBLE, MPI_MAX, communicator); measurement.shells[shell] = { .points = points, .minimum_radius = minimum_radius, .maximum_radius = maximum_radius, .potential_rms_error = - sums[0] > 0.0 ? std::sqrt(sums[1] / sums[0]) - : std::numeric_limits::infinity(), + sums[0] > 0.0 ? std::sqrt(sums[1] / sums[0]) : std::numeric_limits::infinity(), .radial_rms_error = - sums[0] > 0.0 ? std::sqrt(sums[2] / sums[0]) - : std::numeric_limits::infinity(), - .tangential_rms = sums[0] > 0.0 - ? std::sqrt(sums[3] / sums[0]) - : std::numeric_limits::infinity() + sums[0] > 0.0 ? std::sqrt(sums[2] / sums[0]) : std::numeric_limits::infinity(), + .tangential_rms = sums[0] > 0.0 ? std::sqrt(sums[3] / sums[0]) : std::numeric_limits::infinity() }; } @@ -923,90 +726,65 @@ namespace { const mean_field::physics::GravitySolution &solution, const mfem::GridFunction &displacement ) { - mean_field::mapping::DomainMapperStateless::Workspace workspace( - f.mesh->Dimension() - ); + mean_field::mapping::DomainMapperStateless::Workspace workspace(f.mesh->Dimension()); - double local_binding = 0.0; - double local_virial = 0.0; - long long local_invalid_points = 0; - double local_minimum_determinant = - std::numeric_limits::infinity(); - double local_maximum_determinant = - -std::numeric_limits::infinity(); + double local_binding = 0.0; + double local_virial = 0.0; + long long local_invalid_points = 0; + double local_minimum_determinant = std::numeric_limits::infinity(); + double local_maximum_determinant = -std::numeric_limits::infinity(); - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); - const int order = 2 * std::max( - f.gravityPotentialFes->GetMaxElementOrder(), - f.gravityFluxFes->GetMaxElementOrder() - ) + - 8; + const int order = + 2 * std::max(f.gravityPotentialFes->GetMaxElementOrder(), f.gravityFluxFes->GetMaxElementOrder()) + 8; std::array local_status_counts{}; for (int element_id = 0; element_id < f.mesh->GetNE(); ++element_id) { - mfem::ElementTransformation *transformation = - f.mesh->GetElementTransformation(element_id); + mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(element_id); if (transformation->Attribute == vacuum_attribute) { continue; } - const mfem::FiniteElement &displacement_element = - *f.displacementFes->GetFE(element_id); - const mfem::FiniteElement &compactification_element = - *f.compactificationFes->GetFE(element_id); + const mfem::FiniteElement &displacement_element = *f.displacementFes->GetFE(element_id); + const mfem::FiniteElement &compactification_element = *f.compactificationFes->GetFE(element_id); mfem::Array displacement_dofs; mfem::Array compactification_dofs; mfem::DofTransformation *displacement_transform = - f.displacementFes->GetElementVDofs( - element_id, displacement_dofs - ); + f.displacementFes->GetElementVDofs(element_id, displacement_dofs); mfem::DofTransformation *compactification_transform = - f.compactificationFes->GetElementDofs( - element_id, compactification_dofs - ); + f.compactificationFes->GetElementDofs(element_id, compactification_dofs); mfem::Vector element_displacement; mfem::Vector element_compactification; displacement.GetSubVector(displacement_dofs, element_displacement); - f.compactificationCoordinate->GetSubVector( - compactification_dofs, element_compactification - ); + f.compactificationCoordinate->GetSubVector(compactification_dofs, element_compactification); if (displacement_transform != nullptr) { - displacement_transform->InvTransformPrimal( - element_displacement - ); + displacement_transform->InvTransformPrimal(element_displacement); } if (compactification_transform != nullptr) { - compactification_transform->InvTransformPrimal( - element_compactification - ); + compactification_transform->InvTransformPrimal(element_compactification); } - const mean_field::mapping::ElementDisplacementData - displacement_data = mean_field::mapping:: - ElementDisplacementDataFromElementVDofs( - displacement_element, element_displacement - ); - - const mean_field::mapping::ElementCompactificationData - compactification_data( - compactification_element, element_compactification + const mean_field::mapping::ElementDisplacementData displacement_data = + mean_field::mapping::ElementDisplacementDataFromElementVDofs( + displacement_element, element_displacement ); + const mean_field::mapping::ElementCompactificationData compactification_data( + compactification_element, element_compactification + ); + const mean_field::mapping::ElementMappingData mapping_data{ - .displacement = displacement_data, - .compactification = compactification_data + .displacement = displacement_data, .compactification = compactification_data }; - const mfem::IntegrationRule &rule = - mfem::IntRules.Get(transformation->GetGeometryType(), order); + const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), order); for (int q = 0; q < rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &point = rule.IntPoint(q); @@ -1014,26 +792,16 @@ namespace { mean_field::mapping::VolumeMappingContext context; const mean_field::mapping::MappingStatus status = - f.domainMapperStateless->EvaluateVolume( - mapping_data, *transformation, point, workspace, context - ); + f.domainMapperStateless->EvaluateVolume(mapping_data, *transformation, point, workspace, context); - const double mapping_determinant = - context.mapping.mapping_determinant; + const double mapping_determinant = context.mapping.mapping_determinant; if (std::isfinite(mapping_determinant)) { - local_minimum_determinant = std::min( - local_minimum_determinant, mapping_determinant - ); - local_maximum_determinant = std::max( - local_maximum_determinant, mapping_determinant - ); + local_minimum_determinant = std::min(local_minimum_determinant, mapping_determinant); + local_maximum_determinant = std::max(local_maximum_determinant, mapping_determinant); } const int status_index = mapping_status_index(status); - MFEM_VERIFY( - status_index >= 0 && status_index < mapping_status_count, - "Unexpected mapping status." - ); + MFEM_VERIFY(status_index >= 0 && status_index < mapping_status_count, "Unexpected mapping status."); ++local_status_counts[status_index]; if (status != mean_field::mapping::MappingStatus::valid) { @@ -1049,21 +817,15 @@ namespace { mfem::Vector reference_field(3); mfem::Vector physical_field(3); - solution.gradPhi.GetVectorValue( - element_id, point, reference_field - ); + solution.gradPhi.GetVectorValue(element_id, point, reference_field); - mean_field::mapping::MapHDivFluxToPhysical( - context.mapping, reference_field, physical_field - ); + mean_field::mapping::MapHDivFluxToPhysical(context.mapping, reference_field, physical_field); const double rho = density.GetValue(element_id, point); const double phi = solution.phi.GetValue(element_id, point); local_binding += 0.5 * rho * phi * context.quadrature.weight; - local_virial -= - rho * (context.mapping.physical_position * physical_field) * - context.quadrature.weight; + local_virial -= rho * (context.mapping.physical_position * physical_field) * context.quadrature.weight; } } @@ -1071,29 +833,18 @@ namespace { MPI_Comm communicator = f.densityFes->GetComm(); + MPI_Allreduce(&local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, communicator); + MPI_Allreduce(&local_invalid_points, &energies.invalid_points, 1, MPI_LONG_LONG, MPI_SUM, communicator); MPI_Allreduce( - &local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); - MPI_Allreduce( - &local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); - MPI_Allreduce( - &local_invalid_points, &energies.invalid_points, 1, MPI_LONG_LONG, + local_status_counts.data(), energies.mapping_status_counts.data(), mapping_status_count, MPI_LONG_LONG, MPI_SUM, communicator ); MPI_Allreduce( - local_status_counts.data(), energies.mapping_status_counts.data(), - mapping_status_count, MPI_LONG_LONG, MPI_SUM, communicator + &local_minimum_determinant, &energies.minimum_mapping_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); MPI_Allreduce( - &local_minimum_determinant, &energies.minimum_mapping_determinant, - 1, MPI_DOUBLE, MPI_MIN, communicator - ); - MPI_Allreduce( - &local_maximum_determinant, &energies.maximum_mapping_determinant, - 1, MPI_DOUBLE, MPI_MAX, communicator + &local_maximum_determinant, &energies.maximum_mapping_determinant, 1, MPI_DOUBLE, MPI_MAX, communicator ); return energies; @@ -1102,25 +853,22 @@ namespace { TEST_CASE( "New Gravity Monopole Accuracy And Projection Floor", - tags::gravity &tags::analytic_comparison &tags::initialization - &tags::integration &tags::accuracy + tags::gravity &tags::analytic_comparison &tags::initialization &tags::integration &tags::accuracy ) { - auto args = test_utils::setup_args(); - args.p.rtol = 1.0e-13; - args.p.max_iters = std::max(args.p.max_iters, 1000); + auto args = test_utils::setup_args(); + args.p.rtol = 1.0e-13; + args.p.max_iters = std::max(args.p.max_iters, 1000); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr); REQUIRE(f.compactificationCoordinate != nullptr); - const double radius = mean_field::utils::RADIUS; - const double mass = mean_field::utils::MASS; + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; - const double density_value = - mass / ((4.0 / 3.0) * M_PI * radius * radius * radius); + const double density_value = mass / ((4.0 / 3.0) * M_PI * radius * radius * radius); mfem::ParGridFunction displacement(f.displacementFes.get()); displacement = 0.0; @@ -1136,19 +884,14 @@ TEST_CASE( mean_field::analysis::conserve_mass(f, density, mass); f.com = mean_field::analysis::get_com(f, density); - f.Q = mean_field::physics::compute_quadrupole_moment_tensor( - f, density, f.com - ); + f.Q = mean_field::physics::compute_quadrupole_moment_tensor(f, density, f.com); - const mean_field::physics::GravitySolution legacy_solution = - mean_field::physics::grav_potential(f, args, density); + const mean_field::physics::GravitySolution legacy_solution = mean_field::physics::grav_potential(f, args, density); const mean_field::physics::GravitySolution numerical_solution = mean_field::physics::grav_potential_new(f, args, density, displacement); - MonopolePotentialCoefficient potential_coefficient( - f, *f.domainMapperStateless, displacement, mass, radius - ); + MonopolePotentialCoefficient potential_coefficient(f, *f.domainMapperStateless, displacement, mass, radius); mean_field::physics::GravitySolution projected_solution(f); projected_solution.phi = 0.0; @@ -1156,25 +899,20 @@ TEST_CASE( projected_solution.phi.ProjectCoefficient(potential_coefficient); - const mfem::Vector projected_gradient_true = - project_monopole_gradient(f, displacement, mass, radius); + const mfem::Vector projected_gradient_true = project_monopole_gradient(f, displacement, mass, radius); projected_solution.gradPhi.SetFromTrueDofs(projected_gradient_true); - const ShellMeasurement legacy = measure_exterior_shells( - f, legacy_solution, displacement, MappingPath::legacy, mass - ); + const ShellMeasurement legacy = + measure_exterior_shells(f, legacy_solution, displacement, MappingPath::legacy, mass); - const ShellMeasurement numerical = measure_exterior_shells( - f, numerical_solution, displacement, MappingPath::stateless, mass - ); + const ShellMeasurement numerical = + measure_exterior_shells(f, numerical_solution, displacement, MappingPath::stateless, mass); - const ShellMeasurement projected = measure_exterior_shells( - f, projected_solution, displacement, MappingPath::stateless, mass - ); + const ShellMeasurement projected = + measure_exterior_shells(f, projected_solution, displacement, MappingPath::stateless, mass); - const GravitationalEnergies energies = - compute_stellar_energies(f, density, numerical_solution, displacement); + const GravitationalEnergies energies = compute_stellar_energies(f, density, numerical_solution, displacement); mfem::Vector numerical_gradient; mfem::Vector numerical_potential; @@ -1187,15 +925,13 @@ TEST_CASE( projected_solution.gradPhi.GetTrueDofs(projected_gradient); projected_solution.phi.GetTrueDofs(projected_potential); - MPI_Comm communicator = f.gravityFluxFes->GetComm(); + MPI_Comm communicator = f.gravityFluxFes->GetComm(); - const double gradient_projection_gap = mapped_hdiv_relative_error( - f, displacement, numerical_gradient, projected_gradient - ); + const double gradient_projection_gap = + mapped_hdiv_relative_error(f, displacement, numerical_gradient, projected_gradient); - const double potential_projection_gap = global_relative_error( - numerical_potential, projected_potential, communicator - ); + const double potential_projection_gap = + global_relative_error(numerical_potential, projected_potential, communicator); double maximum_numerical_potential_error = 0.0; double maximum_projected_potential_error = 0.0; @@ -1211,69 +947,45 @@ TEST_CASE( const ShellMetrics &numerical_shell = numerical.shells[shell]; const ShellMetrics &projected_shell = projected.shells[shell]; - maximum_numerical_potential_error = std::max( - maximum_numerical_potential_error, - numerical_shell.potential_rms_error - ); + maximum_numerical_potential_error = + std::max(maximum_numerical_potential_error, numerical_shell.potential_rms_error); - maximum_projected_potential_error = std::max( - maximum_projected_potential_error, - projected_shell.potential_rms_error - ); + maximum_projected_potential_error = + std::max(maximum_projected_potential_error, projected_shell.potential_rms_error); - maximum_numerical_radial_error = std::max( - maximum_numerical_radial_error, numerical_shell.radial_rms_error - ); + maximum_numerical_radial_error = std::max(maximum_numerical_radial_error, numerical_shell.radial_rms_error); - maximum_projected_radial_error = std::max( - maximum_projected_radial_error, projected_shell.radial_rms_error - ); + maximum_projected_radial_error = std::max(maximum_projected_radial_error, projected_shell.radial_rms_error); - maximum_numerical_tangential = std::max( - maximum_numerical_tangential, numerical_shell.tangential_rms - ); + maximum_numerical_tangential = std::max(maximum_numerical_tangential, numerical_shell.tangential_rms); - maximum_projected_tangential = std::max( - maximum_projected_tangential, projected_shell.tangential_rms - ); + maximum_projected_tangential = std::max(maximum_projected_tangential, projected_shell.tangential_rms); - report << "shell " << shell << " xi=[" << shell_boundaries[shell] - << ", " << shell_boundaries[shell + 1] << ")\n" - << " legacy radius=[" << old_shell.minimum_radius << ", " - << old_shell.maximum_radius << "]\n" - << " new radius=[" << numerical_shell.minimum_radius << ", " - << numerical_shell.maximum_radius << "]\n" + report << "shell " << shell << " xi=[" << shell_boundaries[shell] << ", " << shell_boundaries[shell + 1] + << ")\n" + << " legacy radius=[" << old_shell.minimum_radius << ", " << old_shell.maximum_radius << "]\n" + << " new radius=[" << numerical_shell.minimum_radius << ", " << numerical_shell.maximum_radius << "]\n" << " potential error: legacy=" << old_shell.potential_rms_error << ", solved=" << numerical_shell.potential_rms_error << ", projection=" << projected_shell.potential_rms_error << '\n' << " radial error: legacy=" << old_shell.radial_rms_error - << ", solved=" << numerical_shell.radial_rms_error - << ", projection=" << projected_shell.radial_rms_error << '\n' + << ", solved=" << numerical_shell.radial_rms_error << ", projection=" << projected_shell.radial_rms_error + << '\n' << " tangential amplitude: legacy=" << old_shell.tangential_rms - << ", solved=" << numerical_shell.tangential_rms - << ", projection=" << projected_shell.tangential_rms << '\n'; + << ", solved=" << numerical_shell.tangential_rms << ", projection=" << projected_shell.tangential_rms + << '\n'; } - const double analytic_energy = - -3.0 * mean_field::utils::G * mass * mass / (5.0 * radius); + const double analytic_energy = -3.0 * mean_field::utils::G * mass * mass / (5.0 * radius); - const double binding_error = std::abs(energies.binding - analytic_energy) / - std::abs(analytic_energy); - const double virial_error = - std::abs(energies.virial - analytic_energy) / std::abs(analytic_energy); - const double consistency_error = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + const double binding_error = std::abs(energies.binding - analytic_energy) / std::abs(analytic_energy); + const double virial_error = std::abs(energies.virial - analytic_energy) / std::abs(analytic_energy); + const double consistency_error = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); INFO(report.str()); - INFO( - "Gradient solution/projection mapped H(div) gap = " - << gradient_projection_gap - ); - INFO( - "Potential solution/projection DOF gap = " << potential_projection_gap - ); + INFO("Gradient solution/projection mapped H(div) gap = " << gradient_projection_gap); + INFO("Potential solution/projection DOF gap = " << potential_projection_gap); INFO("Analytic energy = " << analytic_energy); INFO("Computed binding energy = " << energies.binding); INFO("Computed virial energy = " << energies.virial); @@ -1310,29 +1022,24 @@ TEST_CASE( TEST_CASE( "New Gravity Virial Consistency Across Volume Preserving Deformation", - tags::gravity &tags::self_consistency &tags::initialization - &tags::integration &tags::accuracy + tags::gravity &tags::self_consistency &tags::initialization &tags::integration &tags::accuracy ) { - auto args = test_utils::setup_args(); - args.p.rtol = 1.0e-13; - args.p.max_iters = std::max(args.p.max_iters, 1000); + auto args = test_utils::setup_args(); + args.p.rtol = 1.0e-13; + args.p.max_iters = std::max(args.p.max_iters, 1000); - mean_field::fem::FEM f = - mean_field::fem::setup_fem(args.mesh_file, args, 0); + mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0); REQUIRE(f.mapping != nullptr); REQUIRE(f.domainMapperStateless != nullptr); - const double radius = mean_field::utils::RADIUS; - const double mass = mean_field::utils::MASS; + const double radius = mean_field::utils::RADIUS; + const double mass = mean_field::utils::MASS; - const double central_density = - 15.0 * mass / (8.0 * M_PI * radius * radius * radius); + const double central_density = 15.0 * mass / (8.0 * M_PI * radius * radius * radius); - auto density_function = [central_density, - radius](const mfem::Vector &position) { - const double normalized_radius_squared = - (position * position) / (radius * radius); + auto density_function = [central_density, radius](const mfem::Vector &position) { + const double normalized_radius_squared = (position * position) / (radius * radius); return central_density * std::max(0.0, 1.0 - normalized_radius_squared); }; @@ -1344,8 +1051,7 @@ TEST_CASE( mean_field::analysis::conserve_mass(f, density, mass); - constexpr std::array amplitudes{0.0, 0.02, 0.05, 0.1, - 0.2, 0.5, 1.0}; + constexpr std::array amplitudes{0.0, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0}; std::array consistency_errors{}; std::array normalized_quadrupoles{}; @@ -1361,24 +1067,17 @@ TEST_CASE( const double y_scale = 1.0 - 0.05 * amplitude; const double z_scale = 1.0 / (x_scale * y_scale); - REQUIRE_THAT( - x_scale * y_scale * z_scale, - Catch::Matchers::WithinAbs(1.0, 1.0e-14) - ); + REQUIRE_THAT(x_scale * y_scale * z_scale, Catch::Matchers::WithinAbs(1.0, 1.0e-14)); - auto displacement_function = - [x_scale, y_scale, - z_scale](const mfem::Vector &position, mfem::Vector &value) { - value.SetSize(3); + auto displacement_function = [x_scale, y_scale, z_scale](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); - value(0) = (x_scale - 1.0) * position(0); - value(1) = (y_scale - 1.0) * position(1); - value(2) = (z_scale - 1.0) * position(2); - }; + value(0) = (x_scale - 1.0) * position(0); + value(1) = (y_scale - 1.0) * position(1); + value(2) = (z_scale - 1.0) * position(2); + }; - mfem::VectorFunctionCoefficient displacement_coefficient( - 3, displacement_function - ); + mfem::VectorFunctionCoefficient displacement_coefficient(3, displacement_function); mfem::ParGridFunction displacement(f.displacementFes.get()); displacement.ProjectCoefficient(displacement_coefficient); @@ -1386,83 +1085,58 @@ TEST_CASE( mean_field::physics::update_stiffness_matrix(f); f.com = mean_field::analysis::get_com(f, density); - f.Q = mean_field::physics::compute_quadrupole_moment_tensor( - f, density, f.com - ); + f.Q = mean_field::physics::compute_quadrupole_moment_tensor(f, density, f.com); const mfem::FiniteElementSpace *nodal_space = f.mesh->GetNodalFESpace(); const mean_field::physics::GravitySolution solution = - mean_field::physics::grav_potential_new( - f, args, density, displacement - ); - const GravitationalEnergies energies = - compute_stellar_energies(f, density, solution, displacement); + mean_field::physics::grav_potential_new(f, args, density, displacement); + const GravitationalEnergies energies = compute_stellar_energies(f, density, solution, displacement); CAPTURE(amplitude, x_scale, y_scale, z_scale); INFO( "Mapping status valid = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::valid - )] + << energies.mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::valid)] ); INFO( "Mapping status invalid_dimension = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::invalid_dimension - )] + << energies + .mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::invalid_dimension)] ); INFO( "Mapping status non_finite_input = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::non_finite_input - )] + << energies + .mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::non_finite_input)] ); INFO( - "Mapping status invalid_reference_radius = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::invalid_reference_radius - )] + "Mapping status invalid_reference_radius = " << energies.mapping_status_counts[mapping_status_index( + mean_field::mapping::MappingStatus::invalid_reference_radius + )] ); INFO( - "Mapping status at_compactified_infinity = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::at_compactified_infinity - )] + "Mapping status at_compactified_infinity = " << energies.mapping_status_counts[mapping_status_index( + mean_field::mapping::MappingStatus::at_compactified_infinity + )] ); INFO( - "Mapping status outside_reference_domain = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::outside_reference_domain - )] + "Mapping status outside_reference_domain = " << energies.mapping_status_counts[mapping_status_index( + mean_field::mapping::MappingStatus::outside_reference_domain + )] ); INFO( "Mapping status non_finite_result = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::non_finite_result - )] + << energies + .mapping_status_counts[mapping_status_index(mean_field::mapping::MappingStatus::non_finite_result)] ); INFO( - "Mapping status non_positive_determinant = " - << energies.mapping_status_counts[mapping_status_index( - mean_field::mapping::MappingStatus::non_positive_determinant - )] + "Mapping status non_positive_determinant = " << energies.mapping_status_counts[mapping_status_index( + mean_field::mapping::MappingStatus::non_positive_determinant + )] ); INFO("Total invalid mapping points = " << energies.invalid_points); - INFO( - "Mesh nodal order = " - << (nodal_space != nullptr ? nodal_space->GetMaxElementOrder() : -1) - ); - INFO( - "Displacement order = " << f.displacementFes->GetMaxElementOrder() - ); - INFO( - "Minimum discrete mapping determinant = " - << energies.minimum_mapping_determinant - ); - INFO( - "Maximum discrete mapping determinant = " - << energies.maximum_mapping_determinant - ); + INFO("Mesh nodal order = " << (nodal_space != nullptr ? nodal_space->GetMaxElementOrder() : -1)); + INFO("Displacement order = " << f.displacementFes->GetMaxElementOrder()); + INFO("Minimum discrete mapping determinant = " << energies.minimum_mapping_determinant); + INFO("Maximum discrete mapping determinant = " << energies.maximum_mapping_determinant); REQUIRE(energies.invalid_points == 0); REQUIRE(std::isfinite(energies.binding)); @@ -1470,29 +1144,23 @@ TEST_CASE( REQUIRE(energies.binding < 0.0); REQUIRE(energies.virial < 0.0); - binding_energies[index] = energies.binding; - virial_energies[index] = energies.virial; + binding_energies[index] = energies.binding; + virial_energies[index] = energies.virial; - consistency_errors[index] = - std::abs(energies.binding - energies.virial) / - std::abs(energies.binding); + consistency_errors[index] = std::abs(energies.binding - energies.virial) / std::abs(energies.binding); normalized_quadrupoles[index] = f.Q.FNorm() / (mass * radius * radius); - report << "amplitude=" << amplitude << ", scales=(" << x_scale << ", " - << y_scale << ", " << z_scale + report << "amplitude=" << amplitude << ", scales=(" << x_scale << ", " << y_scale << ", " << z_scale << "), normalized quadrupole=" << normalized_quadrupoles[index] - << ", binding=" << binding_energies[index] - << ", virial=" << virial_energies[index] + << ", binding=" << binding_energies[index] << ", virial=" << virial_energies[index] << ", consistency error=" << consistency_errors[index] << '\n'; } INFO(report.str()); for (std::size_t index = 1; index < amplitudes.size(); ++index) { - CHECK( - normalized_quadrupoles[index] > normalized_quadrupoles[index - 1] - ); + CHECK(normalized_quadrupoles[index] > normalized_quadrupoles[index - 1]); } CHECK(consistency_errors[0] < 1.0e-5); diff --git a/tests/quadrature/policy.cpp b/tests/quadrature/policy.cpp index ae93b04..f228bec 100644 --- a/tests/quadrature/policy.cpp +++ b/tests/quadrature/policy.cpp @@ -40,6 +40,8 @@ namespace { return "gravity_divergence"; case quadrature::Term::gravity_source: return "gravity_source"; + case quadrature::Term::gravity_force: + return "gravity_force"; case quadrature::Term::gravity_boundary: return "gravity_boundary"; case quadrature::Term::centrifugal: @@ -92,9 +94,7 @@ TEST_CASE( "Quadrature Policy Computes Base Orders", tags::unit &tags::quadrature ) { - const quadrature::Policy policy( - quadrature::make_rule_set(quadrature::Mode::production) - ); + const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production)); quadrature::Query generic_query = { .term = quadrature::Term::gravitational_energy, @@ -104,8 +104,7 @@ TEST_CASE( .geometry_weight_order = 5 }; - const quadrature::Resolution generic_resolution = - policy.resolve(generic_query); + const quadrature::Resolution generic_resolution = policy.resolve(generic_query); CHECK(generic_resolution.base_order == 14); CHECK(generic_resolution.boost == 0); CHECK(generic_resolution.order == 14); @@ -141,26 +140,22 @@ TEST_CASE( "Quadrature Policy Composes Global and Term Boosts", tags::unit &tags::quadrature ) { - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production, 3); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 3); rule_set.gravity_hdiv_mass.boost = 5; rule_set.error_norm.boost = 2; const quadrature::Policy policy(rule_set); - const quadrature::Resolution mass_resolution = - policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 7)); + const quadrature::Resolution mass_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 7)); CHECK(mass_resolution.base_order == 7); CHECK(mass_resolution.boost == 8); CHECK(mass_resolution.order == 15); CHECK_FALSE(mass_resolution.used_fixed_order); - const quadrature::Resolution error_resolution = - policy.resolve(make_query(quadrature::Term::error_norm, 7)); + const quadrature::Resolution error_resolution = policy.resolve(make_query(quadrature::Term::error_norm, 7)); CHECK(error_resolution.boost == 5); CHECK(error_resolution.order == 12); - const quadrature::Resolution source_resolution = - policy.resolve(make_query(quadrature::Term::gravity_source, 7)); + const quadrature::Resolution source_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 7)); CHECK(source_resolution.boost == 3); CHECK(source_resolution.order == 10); } @@ -169,23 +164,20 @@ TEST_CASE( "Quadrature Fixed Orders Have Defined Precedence", tags::unit &tags::quadrature ) { - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production, 4); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 4); rule_set.fallback.fixed_order = 17; rule_set.gravity_hdiv_mass.fixed_order = 23; rule_set.gravity_hdiv_mass.boost = 100; rule_set.gravity_source.boost = 100; const quadrature::Policy policy(rule_set); - const quadrature::Resolution term_resolution = - policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 8)); + const quadrature::Resolution term_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 8)); CHECK(term_resolution.base_order == 8); CHECK(term_resolution.boost == 0); CHECK(term_resolution.order == 23); CHECK(term_resolution.used_fixed_order); - const quadrature::Resolution fallback_resolution = - policy.resolve(make_query(quadrature::Term::gravity_source, 8)); + const quadrature::Resolution fallback_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 8)); CHECK(fallback_resolution.base_order == 8); CHECK(fallback_resolution.boost == 0); CHECK(fallback_resolution.order == 17); @@ -200,25 +192,16 @@ TEST_CASE( constexpr int global_boost = 3; for (const quadrature::Mode mode : - {quadrature::Mode::fast, quadrature::Mode::production, - quadrature::Mode::convergence}) { - const quadrature::Policy policy( - quadrature::make_rule_set(mode, global_boost) - ); - const quadrature::Resolution resolution = policy.resolve( - make_query(quadrature::Term::error_norm, base_order) - ); + {quadrature::Mode::fast, quadrature::Mode::production, quadrature::Mode::convergence}) { + const quadrature::Policy policy(quadrature::make_rule_set(mode, global_boost)); + const quadrature::Resolution resolution = policy.resolve(make_query(quadrature::Term::error_norm, base_order)); CHECK(resolution.boost == global_boost); CHECK(resolution.order == base_order + global_boost); } - const quadrature::Policy reference_policy( - quadrature::make_rule_set(quadrature::Mode::reference, global_boost) - ); + const quadrature::Policy reference_policy(quadrature::make_rule_set(quadrature::Mode::reference, global_boost)); const quadrature::Resolution reference_resolution = - reference_policy.resolve( - make_query(quadrature::Term::error_norm, base_order) - ); + reference_policy.resolve(make_query(quadrature::Term::error_norm, base_order)); CHECK(reference_resolution.boost == global_boost + 8); CHECK(reference_resolution.order == base_order + global_boost + 8); } @@ -231,6 +214,7 @@ TEST_CASE( rule_set.gravity_hdiv_mass.boost = 1; rule_set.gravity_divergence.boost = 2; rule_set.gravity_source.boost = 3; + rule_set.gravity_force.boost = 19; rule_set.gravity_boundary.boost = 4; rule_set.centrifugal.boost = 18; rule_set.density_projection.boost = 5; @@ -248,10 +232,11 @@ TEST_CASE( rule_set.error_norm.boost = 17; const quadrature::Policy policy(rule_set); - const std::array, 18> cases = { + const std::array, 19> cases = { {{quadrature::Term::gravity_hdiv_mass, 1}, {quadrature::Term::gravity_divergence, 2}, {quadrature::Term::gravity_source, 3}, + {quadrature::Term::gravity_force, 19}, {quadrature::Term::gravity_boundary, 4}, {quadrature::Term::centrifugal, 18}, {quadrature::Term::density_projection, 5}, @@ -271,8 +256,7 @@ TEST_CASE( for (const auto &[term, expected_boost] : cases) { DYNAMIC_SECTION(get_term_name(term)) { - const quadrature::Resolution resolution = - policy.resolve(make_query(term, 20)); + const quadrature::Resolution resolution = policy.resolve(make_query(term, 20)); CHECK(resolution.boost == expected_boost); CHECK(resolution.order == 20 + expected_boost); } @@ -283,44 +267,26 @@ TEST_CASE( "Quadrature Policy Rejects Invalid Orders", tags::unit &tags::quadrature ) { - const quadrature::Policy policy( - quadrature::make_rule_set(quadrature::Mode::production) - ); + const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production)); - quadrature::Query negative_component_query = { - .term = quadrature::Term::error_norm, .trial_order = -1 - }; - REQUIRE_THROWS_AS( - policy.resolve(negative_component_query), std::invalid_argument - ); + quadrature::Query negative_component_query = {.term = quadrature::Term::error_norm, .trial_order = -1}; + REQUIRE_THROWS_AS(policy.resolve(negative_component_query), std::invalid_argument); - quadrature::Query negative_base_query = { - .term = quadrature::Term::error_norm, .base_order = -1 - }; - REQUIRE_THROWS_AS( - policy.resolve(negative_base_query), std::invalid_argument - ); + quadrature::Query negative_base_query = {.term = quadrature::Term::error_norm, .base_order = -1}; + REQUIRE_THROWS_AS(policy.resolve(negative_base_query), std::invalid_argument); quadrature::RuleSet negative_fixed_rule_set; negative_fixed_rule_set.error_norm.fixed_order = -1; const quadrature::Policy negative_fixed_policy(negative_fixed_rule_set); REQUIRE_THROWS_AS( - negative_fixed_policy.resolve( - make_query(quadrature::Term::error_norm, 3) - ), - std::invalid_argument + negative_fixed_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument ); quadrature::RuleSet negative_resolved_rule_set; negative_resolved_rule_set.fallback.boost = -4; - const quadrature::Policy negative_resolved_policy( - negative_resolved_rule_set - ); + const quadrature::Policy negative_resolved_policy(negative_resolved_rule_set); REQUIRE_THROWS_AS( - negative_resolved_policy.resolve( - make_query(quadrature::Term::error_norm, 3) - ), - std::invalid_argument + negative_resolved_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument ); } @@ -328,15 +294,12 @@ TEST_CASE( "MFEM Rule Factory Returns the Resolved Rule", tags::unit &tags::quadrature ) { - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production, 2); - rule_set.error_norm.boost = 3; + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 2); + rule_set.error_norm.boost = 3; const quadrature::RuleFactory factory{quadrature::Policy(rule_set)}; - const quadrature::MfemRule selected_rule = factory.get( - make_query(quadrature::Term::error_norm, 4), mfem::Geometry::CUBE - ); - const mfem::IntegrationRule &expected_rule = - mfem::IntRules.Get(mfem::Geometry::CUBE, 9); + const quadrature::MfemRule selected_rule = + factory.get(make_query(quadrature::Term::error_norm, 4), mfem::Geometry::CUBE); + const mfem::IntegrationRule &expected_rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 9); REQUIRE(selected_rule.integration_rule != nullptr); CHECK(selected_rule.resolution.base_order == 4); @@ -351,74 +314,49 @@ TEST_CASE( tags::unit &tags::quadrature ) { constexpr int polynomial_degree = 7; - const quadrature::RuleFactory factory{quadrature::Policy( - quadrature::make_rule_set(quadrature::Mode::production) - )}; - const quadrature::MfemRule selected_rule = factory.get( - make_query(quadrature::Term::error_norm, polynomial_degree), - mfem::Geometry::CUBE - ); + const quadrature::RuleFactory factory{quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production))}; + const quadrature::MfemRule selected_rule = + factory.get(make_query(quadrature::Term::error_norm, polynomial_degree), mfem::Geometry::CUBE); double numerical_integral = 0.0; for (int i = 0; i < selected_rule.integration_rule->GetNPoints(); ++i) { - const mfem::IntegrationPoint &integration_point = - selected_rule.integration_rule->IntPoint(i); - numerical_integral += integration_point.weight * - std::pow(integration_point.x, polynomial_degree) * + const mfem::IntegrationPoint &integration_point = selected_rule.integration_rule->IntPoint(i); + numerical_integral += integration_point.weight * std::pow(integration_point.x, polynomial_degree) * std::pow(integration_point.y, polynomial_degree) * std::pow(integration_point.z, polynomial_degree); } - const double one_dimensional_integral = - 1.0 / static_cast(polynomial_degree + 1); - const double analytic_integral = one_dimensional_integral * - one_dimensional_integral * - one_dimensional_integral; - CHECK_THAT( - numerical_integral, - Catch::Matchers::WithinAbs(analytic_integral, 5.0e-14) - ); + const double one_dimensional_integral = 1.0 / static_cast(polynomial_degree + 1); + const double analytic_integral = one_dimensional_integral * one_dimensional_integral * one_dimensional_integral; + CHECK_THAT(numerical_integral, Catch::Matchers::WithinAbs(analytic_integral, 5.0e-14)); } TEST_CASE( "Policy Controlled Hdiv Mass Assembly Matches Overintegrated Reference", tags::quadrature &tags::solver &tags::integration ) { - mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D( - 1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0 - ); + mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::RT_FECollection rt_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); - const mfem::FiniteElement *rt_element = rt_space.GetTypicalFE(); - mfem::ElementTransformation *transformation = - mesh.GetElementTransformation(0); - const int base_order = - 2 * rt_element->GetOrder() + transformation->OrderW(); + const mfem::FiniteElement *rt_element = rt_space.GetTypicalFE(); + mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); + const int base_order = 2 * rt_element->GetOrder() + transformation->OrderW(); - const quadrature::RuleFactory production_factory{quadrature::Policy( - quadrature::make_rule_set(quadrature::Mode::production) - )}; - const quadrature::MfemRule production_rule = production_factory.get( - make_query(quadrature::Term::gravity_hdiv_mass, base_order), - rt_element->GetGeomType() - ); - - quadrature::RuleSet reference_rule_set = - quadrature::make_rule_set(quadrature::Mode::production); - reference_rule_set.gravity_hdiv_mass.boost = 8; - const quadrature::RuleFactory reference_factory{ - quadrature::Policy(reference_rule_set) + const quadrature::RuleFactory production_factory{ + quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production)) }; - const quadrature::MfemRule reference_rule = reference_factory.get( - make_query(quadrature::Term::gravity_hdiv_mass, base_order), - rt_element->GetGeomType() - ); + const quadrature::MfemRule production_rule = + production_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType()); + + quadrature::RuleSet reference_rule_set = quadrature::make_rule_set(quadrature::Mode::production); + reference_rule_set.gravity_hdiv_mass.boost = 8; + const quadrature::RuleFactory reference_factory{quadrature::Policy(reference_rule_set)}; + const quadrature::MfemRule reference_rule = + reference_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType()); mfem::BilinearForm production_mass(&rt_space); auto *production_integrator = new mfem::VectorFEMassIntegrator(); - production_integrator->SetIntegrationRule( - *production_rule.integration_rule - ); + production_integrator->SetIntegrationRule(*production_rule.integration_rule); production_mass.AddDomainIntegrator(production_integrator); production_mass.Assemble(); production_mass.Finalize(); @@ -442,8 +380,7 @@ TEST_CASE( mfem::Vector difference(production_output); difference -= reference_output; - const double relative_difference = - difference.Norml2() / reference_output.Norml2(); + const double relative_difference = difference.Norml2() / reference_output.Norml2(); INFO("Production quadrature order = " << production_rule.resolution.order); INFO("Reference quadrature order = " << reference_rule.resolution.order); INFO("Relative operator difference = " << relative_difference); @@ -454,28 +391,22 @@ TEST_CASE( "HDiv Mass Helper Resolves the MFEM Baseline", tags::unit &tags::quadrature &tags::solver ) { - mfem::Mesh mesh = - mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); + mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); mfem::RT_FECollection rt_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); rule_set.gravity_hdiv_mass.boost = 3; quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))}; - const mfem::FiniteElement &element = *rt_space.GetTypicalFE(); - const mfem::ElementTransformation &transformation = - *mesh.GetElementTransformation(0); + const mfem::FiniteElement &element = *rt_space.GetTypicalFE(); + const mfem::ElementTransformation &transformation = *mesh.GetElementTransformation(0); mfem::VectorFEMassIntegrator integrator; - const quadrature::Resolution resolution = - factory.configure_gravity_hdiv_mass( - integrator, quadrature::QuadratureRole::discretization, element, - transformation - ); - const int expected_base_order = - 2 * element.GetOrder() + transformation.OrderW(); + const quadrature::Resolution resolution = factory.configure_gravity_hdiv_mass( + integrator, quadrature::QuadratureRole::discretization, element, transformation + ); + const int expected_base_order = 2 * element.GetOrder() + transformation.OrderW(); CHECK(resolution.base_order == expected_base_order); CHECK(resolution.boost == 3); @@ -486,33 +417,27 @@ TEST_CASE( "Gravity Divergence Helper Resolves Preconditioner Rule", tags::unit &tags::quadrature &tags::solver ) { - mfem::Mesh mesh = - mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); + mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); mfem::RT_FECollection rt_collection(2, 3); mfem::L2_FECollection l2_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); mfem::FiniteElementSpace l2_space(&mesh, &l2_collection); - quadrature::RuleSet rule_set = - quadrature::make_rule_set(quadrature::Mode::production); + quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); rule_set.gravity_divergence.boost = 2; rule_set.roles.preconditioner.boost = 3; quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))}; - const mfem::FiniteElement &trial_element = *rt_space.GetTypicalFE(); - const mfem::FiniteElement &test_element = *l2_space.GetTypicalFE(); - const mfem::ElementTransformation &transformation = - *mesh.GetElementTransformation(0); + const mfem::FiniteElement &trial_element = *rt_space.GetTypicalFE(); + const mfem::FiniteElement &test_element = *l2_space.GetTypicalFE(); + const mfem::ElementTransformation &transformation = *mesh.GetElementTransformation(0); mfem::VectorFEDivergenceIntegrator integrator; - const quadrature::Resolution resolution = - factory.configure_gravity_divergence( - integrator, quadrature::QuadratureRole::preconditioner, - trial_element, test_element, transformation - ); - const int expected_base_order = std::max(0, trial_element.GetOrder() - 1) + - test_element.GetOrder() + - transformation.OrderW(); + const quadrature::Resolution resolution = factory.configure_gravity_divergence( + integrator, quadrature::QuadratureRole::preconditioner, trial_element, test_element, transformation + ); + const int expected_base_order = + std::max(0, trial_element.GetOrder() - 1) + test_element.GetOrder() + transformation.OrderW(); CHECK(resolution.base_order == expected_base_order); CHECK(resolution.boost == 5); @@ -529,147 +454,93 @@ TEST_CASE( CHECK(mean_field::field::Displacement::Vector::familyOrder == 3); CHECK(mean_field::field::Enthalpy::Scalar::familyOrder == 3); - CHECK((std::same_as< - mean_field::field::Density::Scalar::Space, mean_field::field::L2>)); - CHECK(( - std::same_as< - mean_field::field::Gravity::Potential::Space, mean_field::field::L2> - )); - CHECK((std::same_as< - mean_field::field::Gravity::Flux::Space, mean_field::field::RT>)); - CHECK((std::same_as< - mean_field::field::Displacement::Vector::Space, - mean_field::field::H1>)); - CHECK((std::same_as< - mean_field::field::Enthalpy::Scalar::Space, mean_field::field::H1>)); + CHECK((std::same_as)); + CHECK((std::same_as)); + CHECK((std::same_as)); + CHECK((std::same_as)); + CHECK((std::same_as)); CHECK(mean_field::field::Density::Scalar::rankValue == 0); CHECK(mean_field::field::Gravity::Potential::rankValue == 0); CHECK(mean_field::field::Gravity::Flux::rankValue == 1); CHECK(mean_field::field::Displacement::Vector::rankValue == 1); CHECK(mean_field::field::Enthalpy::Scalar::rankValue == 0); - CHECK( - mean_field::field::Gravity::Flux::familyOrder == - mean_field::field::Gravity::Potential::familyOrder - ); + CHECK(mean_field::field::Gravity::Flux::familyOrder == mean_field::field::Gravity::Potential::familyOrder); - CHECK( - std::is_empty_v> - ); - CHECK( - std::is_empty_v< - mean_field::field::Field> - ); - CHECK( - std::is_empty_v> - ); - CHECK( - std::is_empty_v> - ); + CHECK(std::is_empty_v>); + CHECK(std::is_empty_v>); + CHECK(std::is_empty_v>); + CHECK(std::is_empty_v>); STATIC_CHECK(field::RegisteredQuantity); - STATIC_CHECK( - field::GlobalScalarQuantity - ); + STATIC_CHECK(field::GlobalScalarQuantity); STATIC_CHECK_FALSE(field::FieldQuantity); - STATIC_CHECK( - field::BarotropicConstant::Scalar::storageKind == - field::StorageKind::global_scalar - ); + STATIC_CHECK(field::BarotropicConstant::Scalar::storageKind == field::StorageKind::global_scalar); STATIC_CHECK(field::BarotropicConstant::Scalar::staticBlockSize == 1); - STATIC_CHECK( - field::Enthalpy::Scalar::storageKind == - field::StorageKind::finite_element - ); - STATIC_CHECK( - field::Enthalpy::Scalar::staticBlockSize == field::dynamicBlockSize - ); + STATIC_CHECK(field::Enthalpy::Scalar::storageKind == field::StorageKind::finite_element); + STATIC_CHECK(field::Enthalpy::Scalar::staticBlockSize == field::dynamicBlockSize); - STATIC_CHECK_FALSE( - CanMakeFec< - field::Field, - field::BarotropicConstant::Scalar> - ); + STATIC_CHECK_FALSE(CanMakeFec, field::BarotropicConstant::Scalar>); } TEST_CASE( "Field Forms Produce Typed Quadrature Queries", tags::unit &tags::quadrature ) { - using GravityField = field::Field; - using DensityField = field::Field; - using EnthalpyField = field::Field; + using GravityField = field::Field; + using DensityField = field::Field; + using EnthalpyField = field::Field; - constexpr quadrature::Query hdiv_query = - GravityField::make_query( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::ALL, quadrature::MappingKind::general - ); - constexpr quadrature::Query divergence_query = - GravityField::make_query( - quadrature::QuadratureRole::preconditioner, 2 - ); - constexpr quadrature::Query source_query = - GravityField::make_query( - quadrature::QuadratureRole::projection, 2, {}, - utils::DOMAINS::STELLAR - ); - constexpr quadrature::Query center_of_mass_query = - DensityField::make_query( - quadrature::QuadratureRole::diagnostic, 2, std::array{1}, - utils::DOMAINS::STELLAR - ); - constexpr quadrature::Query eos_closure_query = - EnthalpyField::make_query( - quadrature::QuadratureRole::discretization, 2, - std::array{6}, utils::DOMAINS::STELLAR, - quadrature::MappingKind::general - ); + constexpr quadrature::Query hdiv_query = GravityField::make_query( + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::ALL, quadrature::MappingKind::general + ); + constexpr quadrature::Query divergence_query = GravityField::make_query( + quadrature::QuadratureRole::preconditioner, 2 + ); + constexpr quadrature::Query source_query = GravityField::make_query( + quadrature::QuadratureRole::projection, 2, {}, utils::DOMAINS::STELLAR + ); + constexpr quadrature::Query center_of_mass_query = DensityField::make_query( + quadrature::QuadratureRole::diagnostic, 2, std::array{1}, utils::DOMAINS::STELLAR + ); + constexpr quadrature::Query eos_closure_query = EnthalpyField::make_query( + quadrature::QuadratureRole::discretization, 2, std::array{6}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general + ); constexpr quadrature::Query equilibrium_gravity_query = EnthalpyField::make_query( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::STELLAR, quadrature::MappingKind::general + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query equilibrium_constant_query = EnthalpyField::make_query( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::STELLAR, quadrature::MappingKind::general - ); - constexpr quadrature::Query rotation_query = - EnthalpyField::make_query( - quadrature::QuadratureRole::discretization, 2, - std::array{2}, utils::DOMAINS::STELLAR, - quadrature::MappingKind::general + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); + constexpr quadrature::Query rotation_query = EnthalpyField::make_query( + quadrature::QuadratureRole::discretization, 2, std::array{2}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general + ); constexpr quadrature::Query isobaric_surface_query = EnthalpyField::make_query( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::STELLAR, quadrature::MappingKind::general + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query mesh_extension_query = - field::Field::make_query< - field::Displacement::Form::MeshExtension>( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::ALL, quadrature::MappingKind::general + field::Field::make_query( + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::ALL, quadrature::MappingKind::general ); constexpr quadrature::Query mass_normalization_query = DensityField::make_query( - quadrature::QuadratureRole::discretization, 2, {}, - utils::DOMAINS::STELLAR, quadrature::MappingKind::general + quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query pressure_integral_query = EnthalpyField::make_query( - quadrature::QuadratureRole::diagnostic, 2, std::array{9}, - utils::DOMAINS::STELLAR, quadrature::MappingKind::general + quadrature::QuadratureRole::diagnostic, 2, std::array{9}, utils::DOMAINS::STELLAR, + quadrature::MappingKind::general ); - STATIC_CHECK( - mean_field::field::Gravity::Form::SourceProjection::dynamicOrderCount == - 0 - ); + STATIC_CHECK(mean_field::field::Gravity::Form::SourceProjection::dynamicOrderCount == 0); STATIC_CHECK(hdiv_query.base_order.has_value()); STATIC_CHECK(*hdiv_query.base_order == 8); STATIC_CHECK(*divergence_query.base_order == 6); @@ -684,47 +555,20 @@ TEST_CASE( STATIC_CHECK(*pressure_integral_query.base_order == 14); STATIC_CHECK(*equilibrium_constant_query.base_order == 5); - CHECK( - equilibrium_constant_query.term == - quadrature::Term::hydrostatic_equilibrium - ); + CHECK(equilibrium_constant_query.term == quadrature::Term::hydrostatic_equilibrium); CHECK(hdiv_query.term == mean_field::quadrature::Term::gravity_hdiv_mass); - CHECK( - hdiv_query.role == - mean_field::quadrature::QuadratureRole::discretization - ); + CHECK(hdiv_query.role == mean_field::quadrature::QuadratureRole::discretization); CHECK(hdiv_query.domain == mean_field::utils::DOMAINS::ALL); CHECK(hdiv_query.mapping == mean_field::quadrature::MappingKind::general); CHECK(source_query.term == mean_field::quadrature::Term::gravity_source); - CHECK( - center_of_mass_query.term == - mean_field::quadrature::Term::center_of_mass - ); + CHECK(center_of_mass_query.term == mean_field::quadrature::Term::center_of_mass); CHECK(eos_closure_query.term == mean_field::quadrature::Term::eos_closure); - CHECK( - equilibrium_gravity_query.term == - mean_field::quadrature::Term::hydrostatic_equilibrium - ); - CHECK( - rotation_query.term == - mean_field::quadrature::Term::hydrostatic_equilibrium - ); - CHECK( - isobaric_surface_query.term == - mean_field::quadrature::Term::isobaric_surface - ); - CHECK( - mesh_extension_query.term == - mean_field::quadrature::Term::mesh_extension - ); - CHECK( - mass_normalization_query.term == - mean_field::quadrature::Term::mass_normalization - ); - CHECK( - pressure_integral_query.term == - mean_field::quadrature::Term::pressure_integral - ); + CHECK(equilibrium_gravity_query.term == mean_field::quadrature::Term::hydrostatic_equilibrium); + CHECK(rotation_query.term == mean_field::quadrature::Term::hydrostatic_equilibrium); + CHECK(isobaric_surface_query.term == mean_field::quadrature::Term::isobaric_surface); + CHECK(mesh_extension_query.term == mean_field::quadrature::Term::mesh_extension); + CHECK(mass_normalization_query.term == mean_field::quadrature::Term::mass_normalization); + CHECK(pressure_integral_query.term == mean_field::quadrature::Term::pressure_integral); CHECK_THROWS_AS( (GravityField::make_query( @@ -733,10 +577,8 @@ TEST_CASE( std::invalid_argument ); CHECK_THROWS_AS( - (DensityField::make_query< - mean_field::field::Density::Form::CenterOfMass>( - mean_field::quadrature::QuadratureRole::diagnostic, 2, - std::array{-1} + (DensityField::make_query( + mean_field::quadrature::QuadratureRole::diagnostic, 2, std::array{-1} )), std::invalid_argument ); @@ -755,37 +597,19 @@ TEST_CASE( DensityField::make_fec(3); std::unique_ptr potential_collection = GravityField::make_fec(3); - std::unique_ptr flux_collection = - GravityField::make_fec(3); + std::unique_ptr flux_collection = GravityField::make_fec(3); std::unique_ptr displacement_collection = DisplacementField::make_fec(3); std::unique_ptr enthalpy_collection = EnthalpyField::make_fec(3); - CHECK( - dynamic_cast(density_collection.get()) != - nullptr - ); - CHECK( - dynamic_cast(potential_collection.get()) != - nullptr - ); - CHECK( - dynamic_cast(flux_collection.get()) != nullptr - ); - CHECK( - dynamic_cast(displacement_collection.get()) != - nullptr - ); - CHECK( - dynamic_cast(enthalpy_collection.get()) != - nullptr - ); + CHECK(dynamic_cast(density_collection.get()) != nullptr); + CHECK(dynamic_cast(potential_collection.get()) != nullptr); + CHECK(dynamic_cast(flux_collection.get()) != nullptr); + CHECK(dynamic_cast(displacement_collection.get()) != nullptr); + CHECK(dynamic_cast(enthalpy_collection.get()) != nullptr); - CHECK_THROWS_AS( - (DensityField::make_fec(0)), - std::invalid_argument - ); + CHECK_THROWS_AS((DensityField::make_fec(0)), std::invalid_argument); } TEST_CASE( @@ -806,26 +630,11 @@ TEST_CASE( CHECK(fem.densityFes.get() != fem.gravityPotentialFes.get()); CHECK(fem.densityFec.get() != fem.gravityPotentialFec.get()); - CHECK( - fem.densityFes->GetMaxElementOrder() == - mean_field::field::Density::Scalar::familyOrder - ); - CHECK( - fem.gravityPotentialFes->GetMaxElementOrder() == - mean_field::field::Gravity::Potential::familyOrder - ); - CHECK( - fem.gravityFluxFes->GetMaxElementOrder() == - mean_field::field::Gravity::Flux::familyOrder + 1 - ); - CHECK( - fem.displacementFes->GetMaxElementOrder() == - mean_field::field::Displacement::Vector::familyOrder - ); - CHECK( - fem.enthalpyFes->GetMaxElementOrder() == - mean_field::field::Enthalpy::Scalar::familyOrder - ); + CHECK(fem.densityFes->GetMaxElementOrder() == mean_field::field::Density::Scalar::familyOrder); + CHECK(fem.gravityPotentialFes->GetMaxElementOrder() == mean_field::field::Gravity::Potential::familyOrder); + CHECK(fem.gravityFluxFes->GetMaxElementOrder() == mean_field::field::Gravity::Flux::familyOrder + 1); + CHECK(fem.displacementFes->GetMaxElementOrder() == mean_field::field::Displacement::Vector::familyOrder); + CHECK(fem.enthalpyFes->GetMaxElementOrder() == mean_field::field::Enthalpy::Scalar::familyOrder); CHECK(fem.densityFes->GetVDim() == 1); CHECK(fem.gravityPotentialFes->GetVDim() == 1); @@ -837,22 +646,14 @@ TEST_CASE( REQUIRE(fem.blockTrueOffsets.Size() == 3); CHECK(fem.blockTrueOffsets[0] == 0); CHECK(fem.blockTrueOffsets[1] == fem.displacementFes->GetTrueVSize()); - CHECK( - fem.blockTrueOffsets[2] == - fem.displacementFes->GetTrueVSize() + fem.densityFes->GetTrueVSize() - ); + CHECK(fem.blockTrueOffsets[2] == fem.displacementFes->GetTrueVSize() + fem.densityFes->GetTrueVSize()); REQUIRE(fem.gravityBlockTrueOffsets.Size() == 3); CHECK(fem.gravityBlockTrueOffsets[0] == 0); CHECK(fem.gravityBlockTrueOffsets[1] == fem.gravityFluxFes->GetTrueVSize()); CHECK( - fem.gravityBlockTrueOffsets[2] == - fem.gravityFluxFes->GetTrueVSize() + - fem.gravityPotentialFes->GetTrueVSize() + fem.gravityBlockTrueOffsets[2] == fem.gravityFluxFes->GetTrueVSize() + fem.gravityPotentialFes->GetTrueVSize() ); - CHECK( - fem.gravityContext.source_form->Height() == - fem.gravityPotentialFes->GetTrueVSize() - ); + CHECK(fem.gravityContext.source_form->Height() == fem.gravityPotentialFes->GetTrueVSize()); } \ No newline at end of file diff --git a/tests/surface/isobaric.cpp b/tests/surface/isobaric.cpp new file mode 100644 index 0000000..4a6dc15 --- /dev/null +++ b/tests/surface/isobaric.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +import mean_field; +import test_helpers; + +TEST_CASE( + "Isobaric Surface Resolves Zero Pressure To Zero Enthalpy", + tags::barotrope &tags::unit &tags::surface +) { + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + const mean_field::surface::Isobaric surface; + + const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState); + + CHECK(surface.targetPressure() == 0.0); + CHECK(resolved.targetEnthalpy == 0.0); + CHECK(resolved.residual(0.0) == 0.0); + CHECK(resolved.residual(0.37) == 0.37); + CHECK(resolved.jacobianAction(-0.19) == -0.19); +} + +TEST_CASE( + "Isobaric Surface Resolves Positive Pressure Through The EOS", + tags::barotrope &tags::unit &tags::surface +) { + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + constexpr double targetPressure = 0.03125; + + const mean_field::surface::Isobaric surface(targetPressure); + + const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState); + + const double recoveredPressure = equationOfState.pressure_from_enthalpy(resolved.targetEnthalpy); + + INFO("Resolved surface enthalpy = " << resolved.targetEnthalpy); + INFO("Recovered surface pressure = " << recoveredPressure); + + CHECK(resolved.targetEnthalpy > 0.0); + CHECK(std::abs(recoveredPressure - targetPressure) < 64.0 * std::numeric_limits::epsilon()); + CHECK(resolved.residual(resolved.targetEnthalpy) == 0.0); +} + +TEST_CASE( + "Isobaric Surface Rejects Invalid Pressure Targets", + tags::barotrope &tags::unit &tags::surface +) { + CHECK_THROWS_AS(mean_field::surface::Isobaric(-1.0), std::invalid_argument); + + CHECK_THROWS_AS(mean_field::surface::Isobaric(std::numeric_limits::infinity()), std::invalid_argument); + + CHECK_THROWS_AS(mean_field::surface::Isobaric(std::numeric_limits::quiet_NaN()), std::invalid_argument); +} + +TEST_CASE( + "Surface Base Dispatch Preserves The Isobaric Prescription", + tags::barotrope &tags::unit &tags::surface +) { + const mean_field::eos::Polytrope equationOfState(3.0, 0.25); + + const mean_field::surface::Isobaric isobaric(0.02); + + const mean_field::surface::SurfaceBase &surface = isobaric; + + surface.validate(equationOfState); + + const mean_field::surface::ResolvedSurfaceCondition resolved = surface.resolve(equationOfState); + + CHECK(resolved.targetEnthalpy > 0.0); + CHECK(resolved.residual(resolved.targetEnthalpy) == 0.0); +} diff --git a/tests/test_helpers.cppm b/tests/test_helpers.cppm index 5b79160..1c5ede1 100644 --- a/tests/test_helpers.cppm +++ b/tests/test_helpers.cppm @@ -32,8 +32,7 @@ template struct Tag { return Catch::StringRef(chars.data(), N - 1); } - template - consteval Tag operator&(const Tag &other) const { + template consteval Tag operator&(const Tag &other) const { std::array res{}; std::ranges::copy(chars.begin(), chars.end() - 1, res.begin()); std::ranges::copy(other.chars, res.begin() + (N - 1)); @@ -95,8 +94,7 @@ export namespace gravity_prepared_test_utils { for (int i = 0; i < size; ++i) { const double index = static_cast(i + 1); - vector(i) = std::sin(0.37 * index + phase) + - 0.31 * std::cos(0.19 * index - 0.5 * phase); + vector(i) = std::sin(0.37 * index + phase) + 0.31 * std::cos(0.19 * index - 0.5 * phase); } return vector; @@ -108,20 +106,14 @@ export namespace gravity_prepared_test_utils { ) { mfem::ParGridFunction displacement(f.displacementFes.get()); - auto displacement_function = - [scale](const mfem::Vector &position, mfem::Vector &value) { - value.SetSize(3); - value(0) = scale * (0.04 * position(0) + - 0.01 * position(1) * position(2)); - value(1) = scale * (-0.03 * position(1) + - 0.008 * position(0) * position(2)); - value(2) = scale * (0.02 * position(2) - - 0.006 * position(0) * position(1)); - }; + auto displacement_function = [scale](const mfem::Vector &position, mfem::Vector &value) { + value.SetSize(3); + value(0) = scale * (0.04 * position(0) + 0.01 * position(1) * position(2)); + value(1) = scale * (-0.03 * position(1) + 0.008 * position(0) * position(2)); + value(2) = scale * (0.02 * position(2) - 0.006 * position(0) * position(1)); + }; - mfem::VectorFunctionCoefficient coefficient( - f.mesh->Dimension(), displacement_function - ); + mfem::VectorFunctionCoefficient coefficient(f.mesh->Dimension(), displacement_function); displacement.ProjectCoefficient(coefficient); mfem::Vector displacement_true; @@ -134,10 +126,9 @@ export namespace gravity_prepared_test_utils { const bool stellar ) { mfem::Vector attribute_values(f.mesh->attributes.Max()); - attribute_values = 0.0; + attribute_values = 0.0; - const int vacuum_attribute = - f.domainMapperStateless->GetVacuumElementAttribute(); + const int vacuum_attribute = f.domainMapperStateless->GetVacuumElementAttribute(); for (int i = 0; i < f.mesh->attributes.Size(); ++i) { const int attribute = f.mesh->attributes[i]; @@ -163,10 +154,7 @@ export namespace gravity_prepared_test_utils { const mfem::Vector &second, const double second_scale ) { - MFEM_VERIFY( - first.Size() == second.Size(), - "Cannot combine vectors with different sizes." - ); + MFEM_VERIFY(first.Size() == second.Size(), "Cannot combine vectors with different sizes."); mfem::Vector combination(first); combination *= first_scale; @@ -180,10 +168,7 @@ export namespace gravity_prepared_test_utils { ) { const double local_norm_squared = vector * vector; double global_norm_squared = 0.0; - MPI_Allreduce( - &local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, - communicator - ); + MPI_Allreduce(&local_norm_squared, &global_norm_squared, 1, MPI_DOUBLE, MPI_SUM, communicator); return std::sqrt(global_norm_squared); } @@ -192,16 +177,11 @@ export namespace gravity_prepared_test_utils { const mfem::Vector &second, MPI_Comm communicator ) { - MFEM_VERIFY( - first.Size() == second.Size(), - "Cannot take the dot product of vectors with different sizes." - ); + MFEM_VERIFY(first.Size() == second.Size(), "Cannot take the dot product of vectors with different sizes."); const double local_dot = first * second; double global_dot = 0.0; - MPI_Allreduce( - &local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator - ); + MPI_Allreduce(&local_dot, &global_dot, 1, MPI_DOUBLE, MPI_SUM, communicator); return global_dot; } @@ -210,90 +190,76 @@ export namespace gravity_prepared_test_utils { const mfem::Vector &reference, MPI_Comm communicator ) { - MFEM_VERIFY( - computed.Size() == reference.Size(), - "Cannot compare vectors with different sizes." - ); + MFEM_VERIFY(computed.Size() == reference.Size(), "Cannot compare vectors with different sizes."); mfem::Vector difference(computed); difference -= reference; return global_norm(difference, communicator) / - std::max( - global_norm(reference, communicator), - std::numeric_limits::epsilon() - ); + std::max(global_norm(reference, communicator), std::numeric_limits::epsilon()); } inline double relative_scalar_error( const double computed, const double reference ) { - return std::abs(computed - reference) / - std::max( - std::abs(reference), std::numeric_limits::epsilon() - ); + return std::abs(computed - reference) / std::max(std::abs(reference), std::numeric_limits::epsilon()); } } // namespace gravity_prepared_test_utils export namespace tags { - inline constexpr auto geometry = make_tag("geometry"); - inline constexpr auto physics = make_tag("physics"); - inline constexpr auto unit = make_tag("unit"); - inline constexpr auto mesh = make_tag("mesh"); - inline constexpr auto integration = make_tag("integration"); - inline constexpr auto solver = make_tag("solver"); - inline constexpr auto integrator = make_tag("integrator"); - inline constexpr auto mapping = make_tag("mapping"); - inline constexpr auto utils = make_tag("utils"); - inline constexpr auto mfem_operators = make_tag("operators"); - inline constexpr auto initialization = make_tag("initialization"); - inline constexpr auto accuracy = make_tag("accuracy"); - inline constexpr auto closure = make_tag("closure"); - inline constexpr auto kernels = make_tag("kernels"); + inline constexpr auto geometry = make_tag("geometry"); + inline constexpr auto physics = make_tag("physics"); + inline constexpr auto unit = make_tag("unit"); + inline constexpr auto mesh = make_tag("mesh"); + inline constexpr auto integration = make_tag("integration"); + inline constexpr auto solver = make_tag("solver"); + inline constexpr auto integrator = make_tag("integrator"); + inline constexpr auto mapping = make_tag("mapping"); + inline constexpr auto utils = make_tag("utils"); + inline constexpr auto mfem_operators = make_tag("operators"); + inline constexpr auto initialization = make_tag("initialization"); + inline constexpr auto accuracy = make_tag("accuracy"); + inline constexpr auto closure = make_tag("closure"); + inline constexpr auto kernels = make_tag("kernels"); + inline constexpr auto surface = make_tag("surface"); + inline constexpr auto model = make_tag("model"); - inline constexpr auto legacy_comparison = make_tag("legacy_comparison"); - inline constexpr auto pressure = sub_tag(physics, "pressure"); + inline constexpr auto field = sub_tag(mesh & physics, "field"); - inline constexpr auto hydro = sub_tag(physics, "hydro"); - inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian"); - inline constexpr auto residuals = - sub_tag(integration & physics, "residuals"); - inline constexpr auto volume = sub_tag(mesh & geometry, "volume"); - inline constexpr auto quadrature = - sub_tag(mesh & geometry & solver, "quadrature"); - inline constexpr auto convergence = sub_tag(solver, "convergence"); - inline constexpr auto transformations = - sub_tag(mesh & geometry, "transformations"); + inline constexpr auto legacy_comparison = make_tag("legacy_comparison"); + inline constexpr auto pressure = sub_tag(physics, "pressure"); - inline constexpr auto h_refinement = - sub_tag(mesh & convergence, "h_refinement"); - inline constexpr auto p_refinement = - sub_tag(mesh & convergence, "p_refinement"); + inline constexpr auto hydro = sub_tag(physics, "hydro"); + inline constexpr auto jacobian = sub_tag(integration & physics, "jacobian"); + inline constexpr auto residuals = sub_tag(integration & physics, "residuals"); + inline constexpr auto volume = sub_tag(mesh & geometry, "volume"); + inline constexpr auto quadrature = sub_tag(mesh & geometry & solver, "quadrature"); + inline constexpr auto convergence = sub_tag(solver, "convergence"); + inline constexpr auto transformations = sub_tag(mesh & geometry, "transformations"); - inline constexpr auto analytic_comparison = - sub_tag(solver & physics & residuals, "analytic_comparison"); - inline constexpr auto self_consistency = - sub_tag(solver & physics, "self_consistency"); + inline constexpr auto h_refinement = sub_tag(mesh & convergence, "h_refinement"); + inline constexpr auto p_refinement = sub_tag(mesh & convergence, "p_refinement"); - inline constexpr auto centrifugal = - sub_tag(solver & physics, "centrifugal"); - inline constexpr auto advection = sub_tag(solver & physics, "advection"); - inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis"); - inline constexpr auto gravity = sub_tag(solver & physics, "gravity"); - inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy"); - inline constexpr auto barotrope = sub_tag(physics, "barotrope"); - inline constexpr auto mass_continuity = - sub_tag(solver & physics, "mass_continuity"); - inline constexpr auto pressure_gradient = - sub_tag(solver & physics, "pressure_gradient"); - inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity"); + inline constexpr auto analytic_comparison = sub_tag(solver & physics & residuals, "analytic_comparison"); + inline constexpr auto self_consistency = sub_tag(solver & physics, "self_consistency"); - inline constexpr auto compactification = - sub_tag(mesh & mapping, "compactification"); - inline constexpr auto kelvin = sub_tag(compactification, "kelvin"); + inline constexpr auto centrifugal = sub_tag(solver & physics, "centrifugal"); + inline constexpr auto advection = sub_tag(solver & physics, "advection"); + inline constexpr auto coriolis = sub_tag(solver & physics, "coriolis"); + inline constexpr auto gravity = sub_tag(solver & physics, "gravity"); + inline constexpr auto enthalpy = sub_tag(solver & physics, "enthalpy"); + inline constexpr auto barotrope = sub_tag(physics, "barotrope"); + inline constexpr auto mass_continuity = sub_tag(solver & physics, "mass_continuity"); + inline constexpr auto pressure_gradient = sub_tag(solver & physics, "pressure_gradient"); + inline constexpr auto viscosity = sub_tag(solver & physics, "viscosity"); - inline constexpr auto prepared = sub_tag(solver & physics, "prepared"); - inline constexpr auto contexts = sub_tag(solver, "contexts"); + inline constexpr auto compactification = sub_tag(mesh & mapping, "compactification"); + inline constexpr auto kelvin = sub_tag(compactification, "kelvin"); + + inline constexpr auto prepared = sub_tag(solver & physics, "prepared"); + inline constexpr auto contexts = sub_tag(solver, "contexts"); + + inline constexpr auto domain = sub_tag(mesh, "domain"); } // namespace tags diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 775362e..e03270e 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -65,15 +67,13 @@ std::string ansiToHtml(const std::string &text) { while (i < len) { // Look for ANSI CSI sequence '\033[' or '\x1b[' - if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') && - i + 1 < len && htmlEscaped[i + 1] == '[') { + if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') && i + 1 < len && htmlEscaped[i + 1] == '[') { size_t seqStart = i + 2; size_t seqEnd = htmlEscaped.find('m', seqStart); if (seqEnd != std::string::npos) { - std::string codeStr = - htmlEscaped.substr(seqStart, seqEnd - seqStart); - i = seqEnd + 1; + std::string codeStr = htmlEscaped.substr(seqStart, seqEnd - seqStart); + i = seqEnd + 1; std::istringstream codeStream(codeStr); std::string codeVal; @@ -229,10 +229,28 @@ class CheckReporter : public Catch::StreamingReporterBase { std::vector m_currentFailures; std::vector m_currentInfos; + std::unordered_set m_currentInfoSequences; std::vector m_testRunData; + void captureInfoMessages(Catch::AssertionStats const &assertionStats) { + for (auto const &message : assertionStats.infoMessages) { + if (m_currentInfoSequences.insert(message.sequence).second) { + m_currentInfos.push_back(message.message); + } + } + } + public: - using StreamingReporterBase::StreamingReporterBase; + explicit CheckReporter(Catch::ReporterConfig &&config) : Catch::StreamingReporterBase(std::move(config)) { + // INFO messages are delivered through assertionEnded. Request passing + // assertions as well so HTML logging does not depend on Catch2's -s + // flag. + m_preferences.shouldReportAllAssertions = true; + + // This reporter does not use assertionStarting events. Disabling them + // preserves Catch2's successful-assertion fast path where possible. + m_preferences.shouldReportAllAssertionStarts = false; + } static std::string getDescription() { return "Console reporter with wrapping, tags, and collapsible HTML " @@ -245,28 +263,26 @@ public: std::cout << '\n'; std::cout << std::left << std::setw(85) << "Test Case Name" - << "Status " << std::right << std::setw(8) << "Passed" - << std::setw(8) << "Failed" << '\n'; + << "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << '\n'; std::cout << std::string(121, '-') << '\n'; } void assertionEnded(Catch::AssertionStats const &assertionStats) override { StreamingReporterBase::assertionEnded(assertionStats); - // Capture INFO messages regardless of pass/fail status - for (auto const &msg : assertionStats.infoMessages) { - m_currentInfos.push_back(msg.message); - } + // Capture every INFO message encountered by either a passing or failing + // assertion. Message sequence IDs prevent a scoped INFO from being + // repeated once for every assertion that occurs while it remains + // active. + captureInfoMessages(assertionStats); if (!assertionStats.assertionResult.isOk()) { auto const &result = assertionStats.assertionResult; std::ostringstream oss; - oss << " \033[31m-> FAILED:\033[0m " - << result.getSourceInfo().file << ":" - << result.getSourceInfo().line << '\n'; - oss << " " << result.getTestMacroName() << "( " - << result.getExpression() << " )\n"; + oss << " \033[31m-> FAILED:\033[0m " << result.getSourceInfo().file << ":" << result.getSourceInfo().line + << '\n'; + oss << " " << result.getTestMacroName() << "( " << result.getExpression() << " )\n"; if (result.hasExpandedExpression()) { oss << " with expansion:\n" @@ -290,10 +306,8 @@ public: std::string name = stats.testInfo->name; auto wrappedName = wrapText(name, 83); - std::cout << std::left << std::setw(85) << wrappedName[0] << mark - << " " << std::right << std::setw(8) - << stats.totals.assertions.passed << std::setw(8) - << stats.totals.assertions.failed << '\n'; + std::cout << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right << std::setw(8) + << stats.totals.assertions.passed << std::setw(8) << stats.totals.assertions.failed << '\n'; for (size_t i = 1; i < wrappedName.size(); ++i) { std::cout << " \033[90m↳ \033[0m" // Dim indent arrow @@ -317,12 +331,13 @@ public: } m_testRunData.push_back( - {name, tagsStr, passed, stats.totals.assertions.passed, - stats.totals.assertions.failed, m_currentFailures, m_currentInfos} + {name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, m_currentFailures, + m_currentInfos} ); m_currentFailures.clear(); m_currentInfos.clear(); + m_currentInfoSequences.clear(); } void testRunEnded(Catch::TestRunStats const &_testRunStats) override { @@ -334,27 +349,17 @@ public: auto const &as = _testRunStats.totals.assertions; std::string tc_passed_str = - tc.passed > 0 - ? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m" - : "0 passed"; + tc.passed > 0 ? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m" : "0 passed"; std::string tc_failed_str = - tc.failed > 0 - ? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m" - : "0 failed"; + tc.failed > 0 ? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m" : "0 failed"; std::string as_passed_str = - as.passed > 0 - ? "\033[32m" + std::to_string(as.passed) + " passed\033[0m" - : "0 passed"; + as.passed > 0 ? "\033[32m" + std::to_string(as.passed) + " passed\033[0m" : "0 passed"; std::string as_failed_str = - as.failed > 0 - ? "\033[31m" + std::to_string(as.failed) + " failed\033[0m" - : "0 failed"; + as.failed > 0 ? "\033[31m" + std::to_string(as.failed) + " failed\033[0m" : "0 failed"; - std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str - << ", " << tc.total() << " total\n"; - std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str - << ", " << as.total() << " total\n\n"; + std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str << ", " << tc.total() << " total\n"; + std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str << ", " << as.total() << " total\n\n"; generateHtmlReport(_testRunStats); } @@ -365,68 +370,66 @@ private: if (!html) return; - html - << "\n\n\n" - << "\n" - << "\n" - << "Test Run Summary\n" - << "\n\n\n"; + html << "\n\n\n" + << "\n" + << "\n" + << "Test Run Summary\n" + << "\n\n\n"; html << "

Test Run Summary

\n"; // Summary Cards html << "
\n"; - html << "

Total Cases

" - << stats.totals.testCases.total() << "

\n"; - html << "

Cases Passed

" - << stats.totals.testCases.passed << "

\n"; - html << "

Cases Failed

" - << stats.totals.testCases.failed << "

\n"; + html << "

Total Cases

" << stats.totals.testCases.total() << "

\n"; + html << "

Cases Passed

" << stats.totals.testCases.passed + << "

\n"; + html << "

Cases Failed

" << stats.totals.testCases.failed + << "

\n"; html << "
\n"; for (const auto &test : m_testRunData) { @@ -434,26 +437,21 @@ private: html << "
\n"; html << "
\n"; html << "
\n"; - html << "

" << escapeHtml(test.name) - << "

\n"; + html << "

" << escapeHtml(test.name) << "

\n"; if (!test.tags.empty()) { - html << "
" << escapeHtml(test.tags) - << "
\n"; + html << "
" << escapeHtml(test.tags) << "
\n"; } html << "
\n"; html << "
\n"; - html << " ✓ " - << test.assertionsPassed << " | "; - html << " ✗ " - << test.assertionsFailed << "\n"; + html << " ✓ " << test.assertionsPassed << " | "; + html << " ✗ " << test.assertionsFailed << "\n"; html << "
\n"; html << "
\n"; // Collapsible INFO Messages section with ANSI color rendering if (!test.infoMessages.empty()) { html << "
\n"; - html << " Info Logs (" << test.infoMessages.size() - << ")\n"; + html << " Info Logs (" << test.infoMessages.size() << ")\n"; html << "
";
                 for (const auto &info : test.infoMessages) {
                     html << "[INFO] " << ansiToHtml(info) << "\n";
@@ -465,8 +463,8 @@ private:
             // Collapsible Failures section with ANSI color rendering
             if (!test.failureMessages.empty()) {
                 html << "  
\n"; - html << " Failure Details (" - << test.failureMessages.size() << ")\n"; + html << " Failure Details (" << test.failureMessages.size() + << ")\n"; html << "
";
                 for (const auto &msg : test.failureMessages) {
                     html << ansiToHtml(msg) << "\n";
@@ -542,14 +540,11 @@ int main(
     }
 
     const auto is_reporter_option = [](const std::string &argument) {
-        return argument == "-r" || argument == "--reporter" ||
-               argument.starts_with("-r=") ||
+        return argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") ||
                argument.starts_with("--reporter=");
     };
 
-    if (const bool has_reporter =
-            std::ranges::any_of(catch_arguments, is_reporter_option);
-        !has_reporter) {
+    if (const bool has_reporter = std::ranges::any_of(catch_arguments, is_reporter_option); !has_reporter) {
         catch_arguments.emplace_back("--reporter");
         catch_arguments.emplace_back("check");
     }
@@ -563,9 +558,7 @@ int main(
 
     Catch::Session session;
 
-    if (const int catch_parse_result = session.applyCommandLine(
-            static_cast(catch_argv.size()), catch_argv.data()
-        );
+    if (const int catch_parse_result = session.applyCommandLine(static_cast(catch_argv.size()), catch_argv.data());
         catch_parse_result != 0) {
         return catch_parse_result;
     }
@@ -577,8 +570,7 @@ int main(
 
     const int hdiv_max_q1d = mfem::DeviceDofQuadLimits::Get().HDIV_MAX_Q1D;
     std::cout << "H(div) maximum Q1D = " << hdiv_max_q1d << '\n';
-    std::cout << "Approximate maximum safe integration order = "
-              << 2 * hdiv_max_q1d - 1 << '\n';
+    std::cout << "Approximate maximum safe integration order = " << 2 * hdiv_max_q1d - 1 << '\n';
 
     mean_field::utils::Args test_args = cfg.main();
 
@@ -597,4 +589,4 @@ int main(
     test_utils::set_args(std::move(test_args));
 
     return session.run();
-}
\ No newline at end of file
+}
diff --git a/tests/utils/blocks.cpp b/tests/utils/blocks.cpp
index 112d89d..04c6a5e 100644
--- a/tests/utils/blocks.cpp
+++ b/tests/utils/blocks.cpp
@@ -13,8 +13,7 @@ namespace {
 
     template  struct contains_type;
 
-    template 
-    struct contains_type> : std::false_type { };
+    template  struct contains_type> : std::false_type { };
 
     template 
     struct contains_type>
@@ -23,30 +22,24 @@ namespace {
               std::true_type,
               contains_type>> { };
 
-    template 
-    inline constexpr bool contains_type_v = contains_type::value;
+    template  inline constexpr bool contains_type_v = contains_type::value;
 
     template  struct type_at;
 
-    template 
-    struct type_at<0, blocks::type_list> {
+    template  struct type_at<0, blocks::type_list> {
         using type = Head;
     };
 
-    template 
-    struct type_at> {
+    template  struct type_at> {
         static_assert(index > 0);
-        using type =
-            typename type_at>::type;
+        using type = typename type_at>::type;
     };
 
-    template 
-    using type_at_t = typename type_at::type;
+    template  using type_at_t = typename type_at::type;
 
     template  struct block_row_traits;
 
-    template 
-    struct block_row_traits> {
+    template  struct block_row_traits> {
         using residual                   = Residual;
         using values                     = blocks::type_list;
 
@@ -56,10 +49,7 @@ namespace {
     template 
     inline constexpr bool row_has_exact_values_v =
         block_row_traits::value_count == sizeof...(ExpectedValues) &&
-        (contains_type_v<
-             ExpectedValues,
-             typename block_row_traits::values> &&
-         ...);
+        (contains_type_v::values> && ...);
 
     struct foreign_value final : blocks::value_block_base { };
     struct foreign_residual final : blocks::residual_block_base { };
@@ -69,17 +59,10 @@ TEST_CASE(
     "Block Types Preserve Their Semantic Hierarchy",
     tags::unit &tags::solver &tags::utils
 ) {
-    STATIC_REQUIRE(
-        std::is_base_of_v
-    );
+    STATIC_REQUIRE(std::is_base_of_v);
     STATIC_REQUIRE(std::is_base_of_v);
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base, blocks::residual_block<0>>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v>
-    );
+    STATIC_REQUIRE(std::is_base_of_v>);
+    STATIC_REQUIRE(std::is_base_of_v>);
 
     STATIC_REQUIRE(std::is_base_of_v);
     STATIC_REQUIRE(std::is_base_of_v);
@@ -87,45 +70,19 @@ TEST_CASE(
     STATIC_REQUIRE(std::is_base_of_v);
 
     STATIC_REQUIRE(std::is_base_of_v);
-    STATIC_REQUIRE(
-        std::is_base_of_v
-    );
+    STATIC_REQUIRE(std::is_base_of_v);
     STATIC_REQUIRE(std::is_base_of_v);
     STATIC_REQUIRE(std::is_base_of_v);
     STATIC_REQUIRE(std::is_base_of_v);
 
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::value_block_base, blocks::density::mass::value>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base, blocks::density::mass::residual>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::value_block_base, blocks::gravity::gradient::value>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base, blocks::gravity::gradient::residual>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::value_block_base, blocks::gravity::poisson::value>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base, blocks::gravity::poisson::residual>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::value_block_base, blocks::enthalpy::specific::value>
-    );
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base, blocks::enthalpy::specific::residual>
-    );
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
+    STATIC_REQUIRE(std::is_base_of_v);
 
     STATIC_REQUIRE(std::is_empty_v);
     STATIC_REQUIRE(std::is_empty_v);
@@ -136,41 +93,22 @@ TEST_CASE(
     STATIC_REQUIRE(std::is_empty_v);
     STATIC_REQUIRE(std::is_empty_v);
 
-    STATIC_REQUIRE(
-        std::is_base_of_v
-    );
+    STATIC_REQUIRE(std::is_base_of_v);
+
+    STATIC_REQUIRE(std::is_base_of_v);
+
+    STATIC_REQUIRE(std::is_base_of_v);
 
     STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::term, blocks::barotropic_constant::mass_normalization>
+        std::is_base_of_v
     );
 
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::value_block_base,
-            blocks::barotropic_constant::mass_normalization::value>
-    );
+    STATIC_REQUIRE(blocks::barotropic_constant::mass_normalization::value::static_block_size == 1);
 
-    STATIC_REQUIRE(
-        std::is_base_of_v<
-            blocks::residual_block_base,
-            blocks::barotropic_constant::mass_normalization::residual>
-    );
-
-    STATIC_REQUIRE(
-        blocks::barotropic_constant::mass_normalization::value::
-            static_block_size == 1
-    );
-
-    STATIC_REQUIRE(
-        blocks::barotropic_constant::mass_normalization::residual::
-            static_block_size == 1
-    );
+    STATIC_REQUIRE(blocks::barotropic_constant::mass_normalization::residual::static_block_size == 1);
 
     STATIC_REQUIRE(std::is_empty_v);
-    STATIC_REQUIRE(
-        std::is_empty_v
-    );
+    STATIC_REQUIRE(std::is_empty_v);
 
     CHECK(true);
 }
@@ -180,36 +118,19 @@ TEST_CASE(
     tags::unit &tags::solver &tags::utils
 ) {
     using list = blocks::type_list<
-        blocks::density::mass::value, blocks::displacement::geometry::value,
-        blocks::gravity::gradient::value, blocks::gravity::poisson::value>;
+        blocks::density::mass::value, blocks::displacement::geometry::value, blocks::gravity::gradient::value,
+        blocks::gravity::poisson::value>;
 
     STATIC_REQUIRE(list::size == 4);
-    STATIC_REQUIRE(
-        blocks::type_index_v == 0
-    );
-    STATIC_REQUIRE(
-        blocks::type_index_v == 1
-    );
-    STATIC_REQUIRE(
-        blocks::type_index_v == 2
-    );
-    STATIC_REQUIRE(
-        blocks::type_index_v == 3
-    );
+    STATIC_REQUIRE(blocks::type_index_v == 0);
+    STATIC_REQUIRE(blocks::type_index_v == 1);
+    STATIC_REQUIRE(blocks::type_index_v == 2);
+    STATIC_REQUIRE(blocks::type_index_v == 3);
 
-    STATIC_REQUIRE(
-        std::is_same_v, blocks::density::mass::value>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            type_at_t<1, list>, blocks::displacement::geometry::value>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v, blocks::gravity::gradient::value>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v, blocks::gravity::poisson::value>
-    );
+    STATIC_REQUIRE(std::is_same_v, blocks::density::mass::value>);
+    STATIC_REQUIRE(std::is_same_v, blocks::displacement::geometry::value>);
+    STATIC_REQUIRE(std::is_same_v, blocks::gravity::gradient::value>);
+    STATIC_REQUIRE(std::is_same_v, blocks::gravity::poisson::value>);
 
     CHECK(true);
 }
@@ -218,53 +139,24 @@ TEST_CASE(
     "Gravity Field Form Resolves Value And Residual Blocks",
     tags::unit &tags::solver &tags::utils
 ) {
-    using form = blocks::gravity_field_form;
+    using form                               = blocks::gravity_field_form;
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto displacement_value =
-        blocks::get_value_block(blocks::displacement_field.geometry_term);
-    constexpr auto gravity_gradient_value =
-        blocks::get_value_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value        = blocks::get_value_block(blocks::displacement_field.geometry_term);
+    constexpr auto gravity_gradient_value    = blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_potential_value   = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
     STATIC_REQUIRE(form::value_block_count == 4);
     STATIC_REQUIRE(form::residual_block_count == 2);
 
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t, blocks::value_block<0>>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t,
-            blocks::value_block<1>>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t,
-            blocks::value_block<2>>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t,
-            blocks::value_block<3>>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t,
-            blocks::residual_block<0>>
-    );
-    STATIC_REQUIRE(
-        std::is_same_v<
-            std::remove_cv_t,
-            blocks::residual_block<1>>
-    );
+    STATIC_REQUIRE(std::is_same_v, blocks::value_block<0>>);
+    STATIC_REQUIRE(std::is_same_v, blocks::value_block<1>>);
+    STATIC_REQUIRE(std::is_same_v, blocks::value_block<2>>);
+    STATIC_REQUIRE(std::is_same_v, blocks::value_block<3>>);
+    STATIC_REQUIRE(std::is_same_v, blocks::residual_block<0>>);
+    STATIC_REQUIRE(std::is_same_v, blocks::residual_block<1>>);
 
     CHECK(static_cast(density_value) == 0);
     CHECK(static_cast(displacement_value) == 1);
@@ -278,20 +170,14 @@ TEST_CASE(
     "Resolved Blocks Implicitly Convert For MFEM Interfaces",
     tags::unit &tags::solver &tags::utils
 ) {
-    using form = blocks::gravity_field_form;
+    using form                               = blocks::gravity_field_form;
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto gravity_potential_value   = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
-    auto consume_block_index = [](const int block_index) {
-        return block_index;
-    };
+    auto consume_block_index                 = [](const int block_index) { return block_index; };
 
     CHECK(consume_block_index(density_value) == 0);
     CHECK(consume_block_index(gravity_potential_value) == 3);
@@ -305,35 +191,21 @@ TEST_CASE(
 ) {
     using reordered_form = blocks::block_form<
         blocks::type_list<
-            blocks::gravity::poisson::value, blocks::gravity::gradient::value,
-            blocks::density::mass::value,
+            blocks::gravity::poisson::value, blocks::gravity::gradient::value, blocks::density::mass::value,
             blocks::displacement::geometry::value>,
-        blocks::type_list<
-            blocks::gravity::poisson::residual,
-            blocks::gravity::gradient::residual>>;
+        blocks::type_list>;
 
     constexpr auto gravity_potential_value =
-        blocks::get_value_block(
-            blocks::gravity_field.poisson_term
-        );
+        blocks::get_value_block(blocks::gravity_field.poisson_term);
     constexpr auto gravity_gradient_value =
-        blocks::get_value_block(
-            blocks::gravity_field.gradient_term
-        );
-    constexpr auto density_value = blocks::get_value_block(
-        blocks::density_field.mass_term
-    );
-    constexpr auto displacement_value = blocks::get_value_block(
-        blocks::displacement_field.geometry_term
-    );
+        blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto density_value = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value =
+        blocks::get_value_block(blocks::displacement_field.geometry_term);
     constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(
-            blocks::gravity_field.poisson_term
-        );
+        blocks::get_residual_block(blocks::gravity_field.poisson_term);
     constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(
-            blocks::gravity_field.gradient_term
-        );
+        blocks::get_residual_block(blocks::gravity_field.gradient_term);
 
     STATIC_REQUIRE(static_cast(gravity_potential_value) == 0);
     STATIC_REQUIRE(static_cast(gravity_gradient_value) == 1);
@@ -355,18 +227,12 @@ TEST_CASE(
     const std::array residual_sizes{23, 29};
     const blocks::form_layout layout(value_sizes, residual_sizes);
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto displacement_value =
-        blocks::get_value_block(blocks::displacement_field.geometry_term);
-    constexpr auto gravity_gradient_value =
-        blocks::get_value_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value        = blocks::get_value_block(blocks::displacement_field.geometry_term);
+    constexpr auto gravity_gradient_value    = blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_potential_value   = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
     CHECK(layout.size(density_value) == 11);
     CHECK(layout.size(displacement_value) == 13);
@@ -405,18 +271,12 @@ TEST_CASE(
     const std::array residual_sizes{0, 7};
     const blocks::form_layout layout(value_sizes, residual_sizes);
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto displacement_value =
-        blocks::get_value_block(blocks::displacement_field.geometry_term);
-    constexpr auto gravity_gradient_value =
-        blocks::get_value_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value        = blocks::get_value_block(blocks::displacement_field.geometry_term);
+    constexpr auto gravity_gradient_value    = blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_potential_value   = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
     CHECK(layout.size(density_value) == 3);
     CHECK(layout.size(displacement_value) == 0);
@@ -446,18 +306,12 @@ TEST_CASE(
     const std::array residual_sizes{13, 17};
     const blocks::form_layout layout(value_sizes, residual_sizes);
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto displacement_value =
-        blocks::get_value_block(blocks::displacement_field.geometry_term);
-    constexpr auto gravity_gradient_value =
-        blocks::get_value_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value        = blocks::get_value_block(blocks::displacement_field.geometry_term);
+    constexpr auto gravity_gradient_value    = blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_potential_value   = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
     mfem::BlockVector values(layout.value_offsets());
     mfem::BlockVector residuals(layout.residual_offsets());
@@ -504,24 +358,15 @@ TEST_CASE(
     const std::array residual_sizes{13, 17};
     const blocks::form_layout layout(value_sizes, residual_sizes);
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_value             = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
 
-    mfem::DenseMatrix source_block(
-        layout.size(gravity_poisson_residual), layout.size(density_value)
-    );
+    mfem::DenseMatrix source_block(layout.size(gravity_poisson_residual), layout.size(density_value));
     source_block = 1.0;
 
-    mfem::BlockOperator block_operator(
-        layout.residual_offsets(), layout.value_offsets()
-    );
-    block_operator.SetBlock(
-        gravity_poisson_residual, density_value, &source_block
-    );
+    mfem::BlockOperator block_operator(layout.residual_offsets(), layout.value_offsets());
+    block_operator.SetBlock(gravity_poisson_residual, density_value, &source_block);
 
     mfem::BlockVector values(layout.value_offsets());
     mfem::BlockVector residuals(layout.residual_offsets());
@@ -534,8 +379,7 @@ TEST_CASE(
 
     CHECK(residuals.GetBlock(gravity_gradient_residual).Norml2() == 0.0);
 
-    const mfem::Vector &poisson_residual =
-        residuals.GetBlock(gravity_poisson_residual);
+    const mfem::Vector &poisson_residual = residuals.GetBlock(gravity_poisson_residual);
     REQUIRE(poisson_residual.Size() == residual_sizes[1]);
 
     for (int i = 0; i < poisson_residual.Size(); ++i) {
@@ -555,53 +399,19 @@ TEST_CASE(
 
     STATIC_REQUIRE(blocks::gravity_jacobian_form::size == 2);
 
-    STATIC_REQUIRE(
-        std::is_same_v<
-            typename gradient_traits::residual,
-            blocks::gravity::gradient::residual>
-    );
+    STATIC_REQUIRE(std::is_same_v);
     STATIC_REQUIRE(gradient_traits::value_count == 3);
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::gravity::gradient::value, typename gradient_traits::values>
-    );
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::gravity::poisson::value, typename gradient_traits::values>
-    );
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::displacement::geometry::value,
-            typename gradient_traits::values>
-    );
-    STATIC_REQUIRE_FALSE(
-        contains_type_v<
-            blocks::density::mass::value, typename gradient_traits::values>
-    );
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE_FALSE(contains_type_v);
 
-    STATIC_REQUIRE(
-        std::is_same_v<
-            typename poisson_traits::residual,
-            blocks::gravity::poisson::residual>
-    );
+    STATIC_REQUIRE(std::is_same_v);
     STATIC_REQUIRE(poisson_traits::value_count == 3);
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::gravity::gradient::value, typename poisson_traits::values>
-    );
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::density::mass::value, typename poisson_traits::values>
-    );
-    STATIC_REQUIRE(
-        contains_type_v<
-            blocks::displacement::geometry::value,
-            typename poisson_traits::values>
-    );
-    STATIC_REQUIRE_FALSE(
-        contains_type_v<
-            blocks::gravity::poisson::value, typename poisson_traits::values>
-    );
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE(contains_type_v);
+    STATIC_REQUIRE_FALSE(contains_type_v);
 
     CHECK(true);
 }
@@ -610,36 +420,23 @@ TEST_CASE(
     "Barotropic Equilibrium Form Encodes The Agreed Row And Column Layouts",
     tags::unit &tags::solver &tags::utils
 ) {
-    using form = blocks::barotropic_equilibrium_form;
+    using form                             = blocks::barotropic_equilibrium_form;
 
-    constexpr auto density_value =
-        blocks::get_value_block(blocks::density_field.mass_term);
-    constexpr auto displacement_value =
-        blocks::get_value_block(blocks::displacement_field.geometry_term);
-    constexpr auto gravity_gradient_value =
-        blocks::get_value_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_potential_value =
-        blocks::get_value_block(blocks::gravity_field.poisson_term);
-    constexpr auto enthalpy_value =
-        blocks::get_value_block(blocks::enthalpy_field.specific_term);
-    constexpr auto barotropic_constant_value = blocks::get_value_block(
-        blocks::barotropic_constant_field.mass_normalization_term
-    );
+    constexpr auto density_value           = blocks::get_value_block(blocks::density_field.mass_term);
+    constexpr auto displacement_value      = blocks::get_value_block(blocks::displacement_field.geometry_term);
+    constexpr auto gravity_gradient_value  = blocks::get_value_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_potential_value = blocks::get_value_block(blocks::gravity_field.poisson_term);
+    constexpr auto enthalpy_value          = blocks::get_value_block(blocks::enthalpy_field.specific_term);
+    constexpr auto barotropic_constant_value =
+        blocks::get_value_block(blocks::barotropic_constant_field.mass_normalization_term);
 
-    constexpr auto gravity_gradient_residual =
-        blocks::get_residual_block(blocks::gravity_field.gradient_term);
-    constexpr auto gravity_poisson_residual =
-        blocks::get_residual_block(blocks::gravity_field.poisson_term);
-    constexpr auto density_residual =
-        blocks::get_residual_block(blocks::density_field.mass_term);
-    constexpr auto displacement_residual = blocks::get_residual_block(
-        blocks::displacement_field.geometry_term
-    );
-    constexpr auto enthalpy_residual =
-        blocks::get_residual_block(blocks::enthalpy_field.specific_term);
-    constexpr auto mass_residual = blocks::get_residual_block(
-        blocks::barotropic_constant_field.mass_normalization_term
-    );
+    constexpr auto gravity_gradient_residual = blocks::get_residual_block(blocks::gravity_field.gradient_term);
+    constexpr auto gravity_poisson_residual  = blocks::get_residual_block(blocks::gravity_field.poisson_term);
+    constexpr auto density_residual          = blocks::get_residual_block(blocks::density_field.mass_term);
+    constexpr auto displacement_residual = blocks::get_residual_block(blocks::displacement_field.geometry_term);
+    constexpr auto enthalpy_residual     = blocks::get_residual_block(blocks::enthalpy_field.specific_term);
+    constexpr auto mass_residual =
+        blocks::get_residual_block(blocks::barotropic_constant_field.mass_normalization_term);
 
     STATIC_REQUIRE(form::value_block_count == 6);
     STATIC_REQUIRE(form::residual_block_count == 6);
@@ -678,48 +475,41 @@ TEST_CASE(
 
     STATIC_REQUIRE(
         row_has_exact_values_v<
-            gradient_row, blocks::gravity::gradient::value,
-            blocks::gravity::poisson::value,
+            gradient_row, blocks::gravity::gradient::value, blocks::gravity::poisson::value,
             blocks::displacement::geometry::value>
     );
 
     STATIC_REQUIRE(
         row_has_exact_values_v<
-            poisson_row, blocks::gravity::gradient::value,
-            blocks::density::mass::value, blocks::displacement::geometry::value>
-    );
-
-    STATIC_REQUIRE(
-        row_has_exact_values_v<
-            density_row, blocks::density::mass::value,
-            blocks::enthalpy::specific::value,
+            poisson_row, blocks::gravity::gradient::value, blocks::density::mass::value,
             blocks::displacement::geometry::value>
     );
 
     STATIC_REQUIRE(
         row_has_exact_values_v<
-            displacement_row, blocks::displacement::geometry::value,
-            blocks::enthalpy::specific::value>
-    );
-
-    STATIC_REQUIRE(
-        row_has_exact_values_v<
-            enthalpy_row, blocks::enthalpy::specific::value,
-            blocks::gravity::poisson::value,
-            blocks::displacement::geometry::value,
-            blocks::barotropic_constant::mass_normalization::value>
-    );
-
-    STATIC_REQUIRE(
-        row_has_exact_values_v<
-            mass_row, blocks::density::mass::value,
+            density_row, blocks::density::mass::value, blocks::enthalpy::specific::value,
             blocks::displacement::geometry::value>
     );
 
+    STATIC_REQUIRE(
+        row_has_exact_values_v<
+            displacement_row, blocks::density::mass::value, blocks::displacement::geometry::value,
+            blocks::gravity::gradient::value, blocks::enthalpy::specific::value>
+    );
+
+    STATIC_REQUIRE(
+        row_has_exact_values_v<
+            enthalpy_row, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
+            blocks::displacement::geometry::value, blocks::barotropic_constant::mass_normalization::value>
+    );
+
+    STATIC_REQUIRE(
+        row_has_exact_values_v
+    );
+
     STATIC_REQUIRE_FALSE(
         blocks::has_jacobian_coupling_v<
-            blocks::displacement::geometry::residual,
-            blocks::gravity::poisson::value, jacobian>
+            blocks::displacement::geometry::residual, blocks::gravity::poisson::value, jacobian>
     );
 
     STATIC_REQUIRE_FALSE(
@@ -730,8 +520,7 @@ TEST_CASE(
 
     STATIC_REQUIRE_FALSE(
         blocks::has_jacobian_coupling_v<
-            blocks::barotropic_constant::mass_normalization::residual,
-            blocks::enthalpy::specific::value, jacobian>
+            blocks::barotropic_constant::mass_normalization::residual, blocks::enthalpy::specific::value, jacobian>
     );
 
     CHECK(true);
@@ -751,46 +540,36 @@ TEST_CASE(
     using enthalpy_row     = type_at_t<4, jacobian>;
     using mass_row         = type_at_t<5, jacobian>;
 
-    using missing_row      = blocks::type_list<
-        gradient_row, poisson_row, density_row, displacement_row, enthalpy_row>;
+    using missing_row      = blocks::type_list;
 
-    using duplicate_row = blocks::type_list<
-        gradient_row, poisson_row, density_row, displacement_row, enthalpy_row,
-        enthalpy_row>;
+    using duplicate_row =
+        blocks::type_list;
 
-    using reordered_rows = blocks::type_list<
-        poisson_row, gradient_row, density_row, displacement_row, enthalpy_row,
-        mass_row>;
+    using reordered_rows =
+        blocks::type_list;
 
     using foreign_value_row = blocks::block_row<
-        blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value,
-        blocks::gravity::poisson::value, blocks::displacement::geometry::value,
-        foreign_value>;
+        blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
+        blocks::displacement::geometry::value, foreign_value>;
 
-    using foreign_value_jacobian = blocks::type_list<
-        gradient_row, poisson_row, density_row, displacement_row,
-        foreign_value_row, mass_row>;
+    using foreign_value_jacobian =
+        blocks::type_list;
 
     using duplicate_value_row = blocks::block_row<
-        blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value,
-        blocks::gravity::poisson::value, blocks::displacement::geometry::value,
-        blocks::barotropic_constant::mass_normalization::value,
+        blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
+        blocks::displacement::geometry::value, blocks::barotropic_constant::mass_normalization::value,
         blocks::barotropic_constant::mass_normalization::value>;
 
-    using duplicate_value_jacobian = blocks::type_list<
-        gradient_row, poisson_row, density_row, displacement_row,
-        duplicate_value_row, mass_row>;
+    using duplicate_value_jacobian =
+        blocks::type_list;
 
-    using unknown_residual_row =
-        blocks::block_row;
+    using unknown_residual_row = blocks::block_row;
 
-    using unknown_residual_jacobian = blocks::type_list<
-        gradient_row, poisson_row, density_row, displacement_row, enthalpy_row,
-        unknown_residual_row>;
+    using unknown_residual_jacobian =
+        blocks::type_list;
 
     using duplicate_value_form = blocks::block_form<
-        blocks::type_list<
-            blocks::density::mass::value, blocks::density::mass::value>,
+        blocks::type_list,
         blocks::type_list>;
 
     STATIC_REQUIRE(blocks::block_form_is_valid_v);
@@ -804,17 +583,11 @@ TEST_CASE(
 
     STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form));
 
-    STATIC_REQUIRE_FALSE((
-        blocks::valid_jacobian_form
-    ));
+    STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form));
 
-    STATIC_REQUIRE_FALSE((
-        blocks::valid_jacobian_form
-    ));
+    STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form));
 
-    STATIC_REQUIRE_FALSE((
-        blocks::valid_jacobian_form
-    ));
+    STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form));
 
     CHECK(true);
 }
@@ -826,21 +599,17 @@ TEST_CASE(
     using form = blocks::barotropic_equilibrium_form;
 
     // Columns: [rho, d, g, Phi, h, C]
-    const std::array value_sizes{11, 13, 17,
-                                                               19, 23, 1};
+    const std::array value_sizes{11, 13, 17, 19, 23, 1};
 
     // Rows: [R_g, R_Phi, R_rho, R_d, R_h, R_M]
-    const std::array residual_sizes{17, 19, 11,
-                                                                     13, 23, 1};
+    const std::array residual_sizes{17, 19, 11, 13, 23, 1};
 
     const blocks::form_layout layout(value_sizes, residual_sizes);
 
-    constexpr auto constant_value = blocks::get_value_block(
-        blocks::barotropic_constant_field.mass_normalization_term
-    );
-    constexpr auto mass_residual = blocks::get_residual_block(
-        blocks::barotropic_constant_field.mass_normalization_term
-    );
+    constexpr auto constant_value =
+        blocks::get_value_block(blocks::barotropic_constant_field.mass_normalization_term);
+    constexpr auto mass_residual =
+        blocks::get_residual_block(blocks::barotropic_constant_field.mass_normalization_term);
 
     CHECK(layout.size(constant_value) == 1);
     CHECK(layout.size(mass_residual) == 1);
@@ -851,11 +620,7 @@ TEST_CASE(
     CHECK(layout.value_offsets().Last() == 84);
     CHECK(layout.residual_offsets().Last() == 84);
 
-    const std::array invalid_value_sizes{11, 13,
-                                                                       17, 19,
-                                                                       23, 2};
+    const std::array invalid_value_sizes{11, 13, 17, 19, 23, 2};
 
-    CHECK_THROWS(
-        blocks::form_layout(invalid_value_sizes, residual_sizes)
-    );
+    CHECK_THROWS(blocks::form_layout(invalid_value_sizes, residual_sizes));
 }
\ No newline at end of file
diff --git a/tests/utils/domain.cpp b/tests/utils/domain.cpp
new file mode 100644
index 0000000..cbd7e64
--- /dev/null
+++ b/tests/utils/domain.cpp
@@ -0,0 +1,1135 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+import mean_field;
+import test_helpers;
+
+namespace domain_test_utils {
+    struct UnregisteredDomain final : public mean_field::utils::domain::Domain {
+        static constexpr std::string_view name = "unregistered_domain";
+    };
+
+    struct UnregisteredBoundary final : public mean_field::utils::domain::Boundary {
+        static constexpr std::string_view name = "unregistered_boundary";
+    };
+
+    struct BoundaryEdge {
+        int firstVertexId{-1};
+        int secondVertexId{-1};
+        int attribute{0};
+    };
+
+    struct StroidCase {
+        std::string_view name;
+        int refinementLevels{0};
+        int order{1};
+        double flattening{0.0};
+    };
+
+    template 
+    concept CanFormMaterialList = requires { typename mean_field::utils::domain::MaterialList; };
+
+    template 
+    concept CanFormBoundaryList = requires { typename mean_field::utils::domain::BoundaryList; };
+
+    template 
+    concept CanFormDomainBoundary =
+        requires { typename mean_field::utils::domain::DomainBoundary; };
+
+    template 
+    concept CanFormSchema =
+        requires { typename mean_field::utils::domain::DomainSchema; };
+
+    [[nodiscard]]
+    int vertex_id(
+        const int xElementCount,
+        const int x,
+        const int y
+    ) {
+        return y * (xElementCount + 1) + x;
+    }
+
+    [[nodiscard]]
+    int cell_index(
+        const int xElementCount,
+        const int x,
+        const int y
+    ) {
+        return y * xElementCount + x;
+    }
+
+    [[nodiscard]]
+    int cell_attribute(
+        const std::vector &attributes,
+        const int xElementCount,
+        const int x,
+        const int y
+    ) {
+        return attributes.at(static_cast(cell_index(xElementCount, x, y)));
+    }
+
+    template <
+        typename FirstPredicateT,
+        typename SecondPredicateT>
+    void append_interface_boundaries(
+        std::vector &boundaries,
+        const std::vector &attributes,
+        const int xElementCount,
+        const int yElementCount,
+        FirstPredicateT firstPredicate,
+        SecondPredicateT secondPredicate,
+        const int boundaryAttribute
+    ) {
+        /*
+         * Vertical internal faces.
+         */
+        for (int y = 0; y < yElementCount; ++y) {
+            for (int x = 1; x < xElementCount; ++x) {
+                const int leftAttribute  = cell_attribute(attributes, xElementCount, x - 1, y);
+
+                const int rightAttribute = cell_attribute(attributes, xElementCount, x, y);
+
+                const bool matches       = (firstPredicate(leftAttribute) && secondPredicate(rightAttribute)) ||
+                                           (secondPredicate(leftAttribute) && firstPredicate(rightAttribute));
+
+                if (!matches) {
+                    continue;
+                }
+
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, x, y),
+                     .secondVertexId = vertex_id(xElementCount, x, y + 1),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+
+        /*
+         * Horizontal internal faces.
+         */
+        for (int y = 1; y < yElementCount; ++y) {
+            for (int x = 0; x < xElementCount; ++x) {
+                const int lowerAttribute = cell_attribute(attributes, xElementCount, x, y - 1);
+
+                const int upperAttribute = cell_attribute(attributes, xElementCount, x, y);
+
+                const bool matches       = (firstPredicate(lowerAttribute) && secondPredicate(upperAttribute)) ||
+                                           (secondPredicate(lowerAttribute) && firstPredicate(upperAttribute));
+
+                if (!matches) {
+                    continue;
+                }
+
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, x, y),
+                     .secondVertexId = vertex_id(xElementCount, x + 1, y),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+    }
+
+    template 
+    void append_exterior_boundaries(
+        std::vector &boundaries,
+        const std::vector &attributes,
+        const int xElementCount,
+        const int yElementCount,
+        PredicateT predicate,
+        const int boundaryAttribute
+    ) {
+        /*
+         * Bottom.
+         */
+        for (int x = 0; x < xElementCount; ++x) {
+            if (predicate(cell_attribute(attributes, xElementCount, x, 0))) {
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, x, 0),
+                     .secondVertexId = vertex_id(xElementCount, x + 1, 0),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+
+        /*
+         * Top.
+         */
+        for (int x = 0; x < xElementCount; ++x) {
+            if (predicate(cell_attribute(attributes, xElementCount, x, yElementCount - 1))) {
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, x, yElementCount),
+                     .secondVertexId = vertex_id(xElementCount, x + 1, yElementCount),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+
+        /*
+         * Left.
+         */
+        for (int y = 0; y < yElementCount; ++y) {
+            if (predicate(cell_attribute(attributes, xElementCount, 0, y))) {
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, 0, y),
+                     .secondVertexId = vertex_id(xElementCount, 0, y + 1),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+
+        /*
+         * Right.
+         */
+        for (int y = 0; y < yElementCount; ++y) {
+            if (predicate(cell_attribute(attributes, xElementCount, xElementCount - 1, y))) {
+                boundaries.push_back(
+                    {.firstVertexId  = vertex_id(xElementCount, xElementCount, y),
+                     .secondVertexId = vertex_id(xElementCount, xElementCount, y + 1),
+                     .attribute      = boundaryAttribute}
+                );
+            }
+        }
+    }
+
+    [[nodiscard]]
+    mfem::Mesh make_grid_mesh(
+        const int xElementCount,
+        const int yElementCount,
+        const std::vector &attributes,
+        const std::vector &boundaryEdges
+    ) {
+        REQUIRE(static_cast(attributes.size()) == xElementCount * yElementCount);
+
+        mfem::Mesh mesh(
+            2, (xElementCount + 1) * (yElementCount + 1), xElementCount * yElementCount,
+            static_cast(boundaryEdges.size()), 2
+        );
+
+        for (int y = 0; y <= yElementCount; ++y) {
+            for (int x = 0; x <= xElementCount; ++x) {
+                mesh.AddVertex(static_cast(x), static_cast(y));
+            }
+        }
+
+        for (int y = 0; y < yElementCount; ++y) {
+            for (int x = 0; x < xElementCount; ++x) {
+                const int lowerLeft  = vertex_id(xElementCount, x, y);
+
+                const int lowerRight = vertex_id(xElementCount, x + 1, y);
+
+                const int upperRight = vertex_id(xElementCount, x + 1, y + 1);
+
+                const int upperLeft  = vertex_id(xElementCount, x, y + 1);
+
+                mesh.AddQuad(
+                    lowerLeft, lowerRight, upperRight, upperLeft, cell_attribute(attributes, xElementCount, x, y)
+                );
+            }
+        }
+
+        for (const BoundaryEdge &boundary : boundaryEdges) {
+            mesh.AddBdrSegment(boundary.firstVertexId, boundary.secondVertexId, boundary.attribute);
+        }
+
+        mesh.FinalizeTopology(false);
+        mesh.Finalize(false, false);
+
+        REQUIRE(mesh.GetNBE() == static_cast(boundaryEdges.size()));
+
+        return mesh;
+    }
+
+    [[nodiscard]]
+    std::vector make_layered_attributes() {
+        constexpr int xElementCount = 5;
+        constexpr int yElementCount = 5;
+
+        std::vector attributes(xElementCount * yElementCount, 3);
+
+        for (int y = 1; y <= 3; ++y) {
+            for (int x = 1; x <= 3; ++x) {
+                attributes[static_cast(cell_index(xElementCount, x, y))] = 2;
+            }
+        }
+
+        attributes[static_cast(cell_index(xElementCount, 2, 2))] = 1;
+
+        return attributes;
+    }
+
+    [[nodiscard]]
+    mfem::Mesh make_layered_mesh(
+        const bool includeStellarSurface   = true,
+        const bool includeInfinitySurface  = true,
+        const int stellarSurfaceAttribute  = 1,
+        const int infinitySurfaceAttribute = 2
+    ) {
+        constexpr int xElementCount       = 5;
+        constexpr int yElementCount       = 5;
+
+        const std::vector attributes = make_layered_attributes();
+
+        std::vector boundaries;
+
+        const auto isStellar = [](const int materialId) { return materialId == 1 || materialId == 2; };
+
+        const auto isVacuum  = [](const int materialId) { return materialId == 3; };
+
+        if (includeStellarSurface) {
+            append_interface_boundaries(
+                boundaries, attributes, xElementCount, yElementCount, isStellar, isVacuum, stellarSurfaceAttribute
+            );
+        }
+
+        if (includeInfinitySurface) {
+            append_exterior_boundaries(
+                boundaries, attributes, xElementCount, yElementCount, isVacuum, infinitySurfaceAttribute
+            );
+        }
+
+        return make_grid_mesh(xElementCount, yElementCount, attributes, boundaries);
+    }
+
+    template  void check_schema_is_valid(const mfem::Mesh &mesh) {
+        const auto validation = mean_field::utils::domain::validate_schema(mesh);
+
+        CHECK(validation.relationResults.size() == SchemaT::relationCount);
+
+        for (const auto &relationResult : validation.relationResults) {
+            INFO("Relation index = " << relationResult.relationIndex);
+
+            INFO("Relation name = " << relationResult.relationName);
+
+            INFO("Failure enum = " << static_cast(relationResult.result.failure));
+
+            CHECK(relationResult.valid());
+        }
+
+        CHECK(validation.valid());
+    }
+
+    using AlternateIdSchema = mean_field::utils::domain::DomainSchema<
+        mean_field::utils::domain::MaterialList<
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material>,
+        mean_field::utils::domain::BoundaryList<
+            mean_field::utils::domain::BoundaryAttribute,
+            mean_field::utils::domain::BoundaryAttribute>,
+        mean_field::utils::domain::RelationList<
+            mean_field::utils::domain::Connected,
+            mean_field::utils::domain::Connected,
+            mean_field::utils::domain::Connected,
+            mean_field::utils::domain::Inscribed,
+            mean_field::utils::domain::Inscribed,
+            mean_field::utils::domain::DomainBoundary<
+                mean_field::utils::domain::StellarSurface,
+                mean_field::utils::domain::Stellar,
+                mean_field::utils::domain::Vacuum>,
+            mean_field::utils::domain::
+                DomainBoundary>>;
+
+    [[nodiscard]]
+    stroid::config::MeshConfig make_stroid_config(
+        const int refinementLevels,
+        const int order,
+        const double flattening
+    ) {
+        stroid::config::MeshConfig config;
+
+        config.refinement_levels       = refinementLevels;
+
+        config.order                   = order;
+
+        config.include_external_domain = true;
+
+        config.r_core                  = 0.25;
+
+        config.r_star                  = 1.0;
+
+        config.r_infinity              = 4.0;
+
+        config.flattening              = flattening;
+
+        config.core_id                 = 1;
+
+        config.envelope_id             = 2;
+
+        config.vacuum_id               = 3;
+
+        config.surface_bdr_id          = 1;
+
+        config.inf_bdr_id              = 2;
+
+        config.optimization_methods    = stroid::config::OptimizationMethods{.tmop = false, .smoothstep = true};
+
+        return config;
+    }
+} // namespace domain_test_utils
+
+TEST_CASE(
+    "Domain Types And Composite Domains Preserve Their Semantic Categories",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomain);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomain);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomain);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomainSet);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomainSet);
+
+    STATIC_REQUIRE_FALSE(mean_field::utils::domain::IsDomain);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsDomainOrSet);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsBoundary);
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsBoundary);
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Material Lists Reject Duplicate Ids And Duplicate Semantic Domains",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormMaterialList<
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material>
+    );
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormMaterialList<
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material>
+    );
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormMaterialList<
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material>
+    );
+
+    /*
+     * The schema intentionally imposes no convention on the
+     * numerical range or indexing scheme used by a mesh producer.
+     */
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormMaterialList<
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material,
+            mean_field::utils::domain::Material>
+    );
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Boundary Lists Reject Duplicate Ids And Duplicate Semantic Boundaries",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormBoundaryList<
+            mean_field::utils::domain::BoundaryAttribute,
+            mean_field::utils::domain::BoundaryAttribute>
+    );
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormBoundaryList<
+            mean_field::utils::domain::BoundaryAttribute,
+            mean_field::utils::domain::BoundaryAttribute>
+    );
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormBoundaryList<
+            mean_field::utils::domain::BoundaryAttribute,
+            mean_field::utils::domain::BoundaryAttribute>
+    );
+
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormBoundaryList<
+            mean_field::utils::domain::BoundaryAttribute,
+            mean_field::utils::domain::BoundaryAttribute>
+    );
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Domain Boundary Relations Accept Exactly One Or Two Domains",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormDomainBoundary<
+            mean_field::utils::domain::InfinitySurface, mean_field::utils::domain::Vacuum>
+    );
+
+    STATIC_REQUIRE(
+        domain_test_utils::CanFormDomainBoundary<
+            mean_field::utils::domain::StellarSurface, mean_field::utils::domain::Stellar,
+            mean_field::utils::domain::Vacuum>
+    );
+
+    STATIC_REQUIRE_FALSE(domain_test_utils::CanFormDomainBoundary);
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormDomainBoundary<
+            mean_field::utils::domain::StellarSurface, mean_field::utils::domain::Core,
+            mean_field::utils::domain::Envelope, mean_field::utils::domain::Vacuum>
+    );
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Domain Schemas Reject Relations That Reference Unregistered Entities",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    using IncompleteMaterials = mean_field::utils::domain::MaterialList<
+        mean_field::utils::domain::Material,
+        mean_field::utils::domain::Material>;
+
+    using CompleteMaterials = mean_field::utils::domain::MaterialList<
+        mean_field::utils::domain::Material,
+        mean_field::utils::domain::Material,
+        mean_field::utils::domain::Material>;
+
+    using CompleteBoundaries = mean_field::utils::domain::BoundaryList<
+        mean_field::utils::domain::BoundaryAttribute,
+        mean_field::utils::domain::BoundaryAttribute>;
+
+    using InfinityOnlyBoundary = mean_field::utils::domain::BoundaryList<
+        mean_field::utils::domain::BoundaryAttribute>;
+
+    using MissingEnvelopeRelation = mean_field::utils::domain::RelationList<
+        mean_field::utils::domain::Connected>;
+
+    using MissingBoundaryRelation = mean_field::utils::domain::RelationList>;
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormSchema
+    );
+
+    STATIC_REQUIRE_FALSE(
+        domain_test_utils::CanFormSchema
+    );
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Core Envelope Vacuum Schema Exposes Exact Compile Time And Runtime Metadata",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    using SchemaT = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
+
+    STATIC_REQUIRE(mean_field::utils::domain::IsSchema);
+
+    STATIC_REQUIRE(SchemaT::materialCount == 3);
+
+    STATIC_REQUIRE(SchemaT::boundaryCount == 2);
+
+    STATIC_REQUIRE(SchemaT::relationCount == 7);
+
+    constexpr auto materials  = SchemaT::materials();
+
+    constexpr auto boundaries = SchemaT::boundaries();
+
+    STATIC_REQUIRE(materials[0].name == std::string_view{"core"});
+
+    STATIC_REQUIRE(materials[0].id == 1);
+
+    STATIC_REQUIRE(materials[1].name == std::string_view{"envelope"});
+
+    STATIC_REQUIRE(materials[1].id == 2);
+
+    STATIC_REQUIRE(materials[2].name == std::string_view{"vacuum"});
+
+    STATIC_REQUIRE(materials[2].id == 3);
+
+    STATIC_REQUIRE(boundaries[0].name == std::string_view{"stellar_surface"});
+
+    STATIC_REQUIRE(boundaries[0].id == 1);
+
+    STATIC_REQUIRE(boundaries[1].name == std::string_view{"infinity_surface"});
+
+    STATIC_REQUIRE(boundaries[1].id == 2);
+
+    STATIC_REQUIRE(SchemaT::template contains_domain());
+
+    STATIC_REQUIRE(SchemaT::template contains_domain());
+
+    STATIC_REQUIRE(SchemaT::template contains_domain());
+
+    STATIC_REQUIRE(SchemaT::template attribute_belongs_to(1));
+
+    STATIC_REQUIRE(SchemaT::template attribute_belongs_to(2));
+
+    STATIC_REQUIRE_FALSE(SchemaT::template attribute_belongs_to(3));
+
+    STATIC_REQUIRE(SchemaT::template attribute_belongs_to(1));
+
+    STATIC_REQUIRE(SchemaT::template attribute_belongs_to(2));
+
+    STATIC_REQUIRE(SchemaT::template attribute_belongs_to(3));
+
+    STATIC_REQUIRE(SchemaT::template contains_boundary());
+
+    STATIC_REQUIRE(SchemaT::template contains_boundary());
+
+    STATIC_REQUIRE(SchemaT::template boundary_attribute() == 1);
+
+    STATIC_REQUIRE(SchemaT::template boundary_attribute() == 2);
+
+    CHECK(true);
+}
+
+TEST_CASE(
+    "Connected Accepts Face Connected Atomic And Composite Domains",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    mfem::Mesh mesh       = domain_test_utils::make_layered_mesh();
+
+    const auto coreResult = mean_field::utils::domain::
+        RelationValidator>::template validate<
+            mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema>(mesh);
+
+    REQUIRE(coreResult);
+    REQUIRE(coreResult.connectedDiagnostics.has_value());
+
+    CHECK(coreResult.connectedDiagnostics->domainElementCount == 1);
+
+    CHECK(coreResult.connectedDiagnostics->visitedElementCount == 1);
+
+    const auto stellarResult = mean_field::utils::domain::
+        RelationValidator>::template validate<
+            mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema>(mesh);
+
+    REQUIRE(stellarResult);
+    REQUIRE(stellarResult.connectedDiagnostics.has_value());
+
+    CHECK(stellarResult.connectedDiagnostics->domainElementCount == 9);
+
+    CHECK(stellarResult.connectedDiagnostics->visitedElementCount == 9);
+}
+
+TEST_CASE(
+    "Connected Rejects An Absent Domain",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 2}, {});
+
+    const auto result     = mean_field::utils::domain::
+        RelationValidator>::template validate<
+            mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema>(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::DomainAbsent);
+
+    REQUIRE(result.connectedDiagnostics.has_value());
+
+    CHECK(result.connectedDiagnostics->domainElementCount == 0);
+
+    CHECK(result.connectedDiagnostics->visitedElementCount == 0);
+}
+
+TEST_CASE(
+    "Connected Rejects Multiple Face Disconnected Components",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 1, {1, 2, 1}, {});
+
+    const auto result     = mean_field::utils::domain::
+        RelationValidator>::template validate<
+            mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema>(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::DomainDisconnected);
+
+    REQUIRE(result.connectedDiagnostics.has_value());
+
+    CHECK(result.connectedDiagnostics->domainElementCount == 2);
+
+    CHECK(result.connectedDiagnostics->visitedElementCount == 1);
+
+    CHECK(result.connectedDiagnostics->elementId >= 0);
+}
+
+TEST_CASE(
+    "Inscribed Accepts Nested Atomic And Composite Domains",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
+
+    const auto coreResult = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK(coreResult);
+
+    const auto stellarResult = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK(stellarResult);
+}
+
+TEST_CASE(
+    "Inscribed Rejects An Absent Inner Domain",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 2, {2, 2, 2, 2}, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::InnerDomainAbsent);
+}
+
+TEST_CASE(
+    "Inscribed Rejects An Absent Outer Domain",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(1, 1, {1}, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::OuterDomainAbsent);
+}
+
+TEST_CASE(
+    "Inscribed Rejects An Inner Domain Touching The Computational Boundary",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 2, {1, 2, 2, 2}, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::InnerDomainTouchesMeshBoundary);
+
+    REQUIRE(result.inscribedDiagnostics.has_value());
+
+    CHECK(result.inscribedDiagnostics->faceId >= 0);
+
+    CHECK(result.inscribedDiagnostics->innerElementId >= 0);
+
+    CHECK(result.inscribedDiagnostics->adjacentElementId == -1);
+}
+
+TEST_CASE(
+    "Inscribed Rejects An Inner Domain Touching An Unexpected Material",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    std::vector attributes{2, 2, 2, 2, 1, 3, 2, 2, 2};
+
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 3, attributes, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator<
+        mean_field::utils::domain::Inscribed>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::InnerDomainTouchesUnexpectedMaterial);
+
+    REQUIRE(result.inscribedDiagnostics.has_value());
+
+    CHECK(result.inscribedDiagnostics->adjacentMaterialId == 3);
+}
+
+TEST_CASE(
+    "Domain Boundary Accepts A Complete Internal Stellar Vacuum Interface",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    std::vector boundaries{{.firstVertexId = 1, .secondVertexId = 4, .attribute = 1}};
+
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 3}, boundaries);
+
+    const auto result     = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK(result);
+
+    /*
+     * Interface ordering is intentionally semantic rather
+     * than oriented.
+     */
+    const auto reversedResult = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK(reversedResult);
+}
+
+TEST_CASE(
+    "Domain Boundary Accepts A Complete Exterior Vacuum Boundary",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const std::vector attributes{3};
+
+    std::vector boundaries;
+
+    domain_test_utils::append_exterior_boundaries(
+        boundaries, attributes, 1, 1, [](const int materialId) { return materialId == 3; }, 2
+    );
+
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(1, 1, attributes, boundaries);
+
+    const auto result     = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK(result);
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects A Tagged Internal Face For An Exterior Boundary",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh =
+        domain_test_utils::make_grid_mesh(2, 1, {3, 3}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 2}});
+
+    const auto result = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(
+        result.failure == mean_field::utils::domain::RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology
+    );
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects A Tagged Exterior Face Of The Wrong Material",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh =
+        domain_test_utils::make_grid_mesh(1, 1, {2}, {{.firstVertexId = 0, .secondVertexId = 1, .attribute = 2}});
+
+    const auto result = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(
+        result.failure ==
+        mean_field::utils::domain::RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedMaterial
+    );
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects A Tagged Internal Interface With Unexpected Materials",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh =
+        domain_test_utils::make_grid_mesh(2, 1, {1, 2}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 1}});
+
+    const auto result = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(
+        result.failure ==
+        mean_field::utils::domain::RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedMaterial
+    );
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects An Untagged Expected Interface",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 3}, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged);
+
+    REQUIRE(result.domainBoundaryDiagnostics.has_value());
+
+    CHECK(result.domainBoundaryDiagnostics->faceId >= 0);
+
+    CHECK(result.domainBoundaryDiagnostics->boundaryElementId == -1);
+
+    CHECK_FALSE(result.domainBoundaryDiagnostics->actualBoundaryAttribute.has_value());
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects An Expected Interface With The Wrong Attribute",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh =
+        domain_test_utils::make_grid_mesh(2, 1, {2, 3}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 9}});
+
+    const auto result = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(
+        result.failure ==
+        mean_field::utils::domain::RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongAttribute
+    );
+
+    REQUIRE(result.domainBoundaryDiagnostics.has_value());
+
+    REQUIRE(result.domainBoundaryDiagnostics->actualBoundaryAttribute.has_value());
+
+    CHECK(*result.domainBoundaryDiagnostics->actualBoundaryAttribute == 9);
+
+    CHECK(result.domainBoundaryDiagnostics->expectedBoundaryAttribute == 1);
+}
+
+TEST_CASE(
+    "Domain Boundary Rejects A Relation That Is Not Realized Anywhere",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 2}, {});
+
+    const auto result     = mean_field::utils::domain::RelationValidator>::
+        template validate(mesh);
+
+    CHECK_FALSE(result);
+
+    CHECK(result.failure == mean_field::utils::domain::RelationValidationFailure::DomainBoundaryAbsent);
+}
+
+TEST_CASE(
+    "Complete Schema Validation Accepts A Synthetic Core Envelope Vacuum Mesh",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
+
+    const auto validation =
+        mean_field::utils::domain::validate_schema(mesh);
+
+    REQUIRE(validation.valid());
+
+    REQUIRE(validation.relationResults.size() == 7);
+
+    CHECK(validation.failed_relation_count() == 0);
+
+    CHECK(validation.passed_relation_count() == 7);
+
+    CHECK_FALSE(validation.first_failed_relation_index().has_value());
+
+    const std::array expectedRelationNames{"connected",      "connected", "connected",
+                                                                "inscribed",      "inscribed", "domain_boundary",
+                                                                "domain_boundary"};
+
+    for (std::size_t relationIndex = 0; relationIndex < expectedRelationNames.size(); ++relationIndex) {
+        CHECK(validation.relationResults[relationIndex].relationIndex == relationIndex);
+
+        CHECK(validation.relationResults[relationIndex].relationName == expectedRelationNames[relationIndex]);
+
+        CHECK(validation.relationResults[relationIndex].valid());
+    }
+}
+
+TEST_CASE(
+    "Complete Schema Validation Evaluates Every Relation After A Failure",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    /*
+     * All material topology and the outer vacuum boundary are valid.
+     * Only the Stellar/Vacuum boundary tagging is intentionally absent.
+     */
+    const mfem::Mesh mesh = domain_test_utils::make_layered_mesh(false, true);
+
+    const auto validation =
+        mean_field::utils::domain::validate_schema(mesh);
+
+    CHECK_FALSE(validation.valid());
+
+    REQUIRE(validation.relationResults.size() == 7);
+
+    CHECK(validation.failed_relation_count() == 1);
+
+    CHECK(validation.passed_relation_count() == 6);
+
+    REQUIRE(validation.first_failed_relation_index().has_value());
+
+    CHECK(*validation.first_failed_relation_index() == 5);
+
+    for (std::size_t relationIndex = 0; relationIndex < 7; ++relationIndex) {
+        CAPTURE(relationIndex);
+
+        if (relationIndex == 5) {
+            CHECK_FALSE(validation.relationResults[relationIndex].valid());
+
+            CHECK(
+                validation.relationResults[relationIndex].result.failure ==
+                mean_field::utils::domain::RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged
+            );
+
+            continue;
+        }
+
+        CHECK(validation.relationResults[relationIndex].valid());
+    }
+}
+
+TEST_CASE(
+    "STROID Meshes Satisfy The Core Envelope Vacuum Domain Schema",
+    tags::integration &tags::mesh &tags::utils &tags::domain
+) {
+    const std::array testCases{
+        domain_test_utils::StroidCase{
+            .name = "spherical_low_order", .refinementLevels = 0, .order = 1, .flattening = 0.0
+        },
+        domain_test_utils::StroidCase{.name = "oblate", .refinementLevels = 0, .order = 2, .flattening = 0.15},
+        domain_test_utils::StroidCase{.name = "refined_oblate", .refinementLevels = 1, .order = 2, .flattening = 0.10}
+    };
+
+    for (const domain_test_utils::StroidCase &testCase : testCases) {
+        INFO("STROID case = " << testCase.name);
+
+        INFO("Refinement levels = " << testCase.refinementLevels);
+
+        INFO("Order = " << testCase.order);
+
+        INFO("Flattening = " << testCase.flattening);
+
+        const stroid::config::MeshConfig config =
+            domain_test_utils::make_stroid_config(testCase.refinementLevels, testCase.order, testCase.flattening);
+
+        stroid::StroidMesh stroidMesh = stroid::GenerateMesh(config);
+
+        REQUIRE(stroidMesh.reference_mesh != nullptr);
+
+        REQUIRE(stroidMesh.mesh != nullptr);
+
+        /*
+         * Validate both the reference topology and the projected
+         * physical mesh. The mapping/projection must not alter
+         * material or boundary semantics.
+         */
+        domain_test_utils::check_schema_is_valid(
+            *stroidMesh.reference_mesh
+        );
+
+        domain_test_utils::check_schema_is_valid(
+            *stroidMesh.mesh
+        );
+    }
+}
+
+TEST_CASE(
+    "STROID Material And Boundary Id Conventions Are Fully Schema Driven",
+    tags::integration &tags::mesh &tags::utils &tags::domain
+) {
+    stroid::config::MeshConfig config = domain_test_utils::make_stroid_config(0, 1, 0.0);
+
+    config.core_id                    = 11;
+
+    config.envelope_id                = 17;
+
+    config.vacuum_id                  = 29;
+
+    config.surface_bdr_id             = 101;
+
+    config.inf_bdr_id                 = 203;
+
+    stroid::StroidMesh stroidMesh     = stroid::GenerateMesh(config);
+
+    REQUIRE(stroidMesh.reference_mesh != nullptr);
+
+    REQUIRE(stroidMesh.mesh != nullptr);
+
+    /*
+     * The same semantic topology must validate when a mesh generator
+     * uses an entirely different attribute numbering convention.
+     */
+    domain_test_utils::check_schema_is_valid(*stroidMesh.reference_mesh);
+
+    domain_test_utils::check_schema_is_valid(*stroidMesh.mesh);
+
+    /*
+     * Conversely, the production 1/2/3 + 1/2 schema must not silently
+     * accept a mesh generated under another numbering convention.
+     */
+    const auto productionValidation =
+        mean_field::utils::domain::validate_schema(
+            *stroidMesh.mesh
+        );
+
+    CHECK_FALSE(productionValidation.valid());
+
+    CHECK(productionValidation.failed_relation_count() > 0);
+}
+
+TEST_CASE(
+    "Complete Schema Validation Rejects A Mesh Without Vacuum",
+    tags::unit &tags::mesh &tags::utils &tags::domain
+) {
+    const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 3, {2, 2, 2, 2, 1, 2, 2, 2, 2}, {});
+
+    const auto validation =
+        mean_field::utils::domain::validate_schema(mesh);
+
+    CHECK_FALSE(validation.valid());
+
+    REQUIRE(validation.relationResults.size() == 7);
+
+    /*
+     * Connected
+     */
+    CHECK_FALSE(validation.relationResults[2].valid());
+
+    CHECK(
+        validation.relationResults[2].result.failure ==
+        mean_field::utils::domain::RelationValidationFailure::DomainAbsent
+    );
+
+    /*
+     * Inscribed
+     */
+    CHECK_FALSE(validation.relationResults[4].valid());
+
+    CHECK(
+        validation.relationResults[4].result.failure ==
+        mean_field::utils::domain::RelationValidationFailure::OuterDomainAbsent
+    );
+}
\ No newline at end of file