Compare commits

..

8 Commits

Author SHA1 Message Date
76818f2f82 feat(libmeanfield): variadic refactor
also added normaliztion operator
2026-09-06 10:15:00 -04:00
71423d543f feat(preconditioner): major work on preconditioner system
first preconditioner MVP
2026-09-04 07:54:10 -04:00
25510008dd perf(jacobian-action): major updates to jacobian action application by removing redudant quadrature work. ~5x increase in speed 2026-09-02 17:01:50 -04:00
85500fef3b feat(surface): surface deformation prescriptions
restricted the unknown state vector to surface deformation and implemented one prescription, NodalRadialSurface, while the full volumetric displacment field is reconstructed analytically from that. This reduced the number of degrees of freedom in the system by a factor of 80 while also removing many null vectors from the system.
2026-09-01 11:50:13 -04:00
0a7f18c5c7 feat(surface): major work on implementing surface constraints in a presciption agnostic manner 2026-08-30 16:41:14 -04:00
36adfa1174 feat(FieldDofMap): Completed FieldDofMap migration
also removed legacy BarotropicPolytrope implementation
2026-08-29 08:56:36 -04:00
177ae8b38a feat(support): continued migration to new field dof support system
mass normaliztion, gravity, displacement, and hydrostatic equilibrium are now migrated
2026-08-24 15:44:24 -04:00
0f3ca8050b feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
2026-08-23 10:13:53 -04:00
269 changed files with 98847 additions and 20943 deletions

View File

@@ -1,10 +1,19 @@
cmake_minimum_required(VERSION 3.28)
project(MeanField CXX)
project(MeanField C CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
option(MEAN_FIELD_ENABLE_PROFILING "Enable low-overhead scoped profiling instrumentation" OFF)
option(MEAN_FIELD_ENABLE_IPO "Enable interprocedural optimization in release builds" ON)
set(MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT 0 CACHE STRING
"Uniform increment applied to every registered finite-element family order")
if (NOT MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT MATCHES "^[0-9]+$")
message(FATAL_ERROR "MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT must be a non-negative integer")
endif ()
add_compile_options(
-gdwarf-4
-Wno-unused-parameter
@@ -31,9 +40,16 @@ find_package(PkgConfig REQUIRED)
pkg_check_modules(stroid REQUIRED IMPORTED_TARGET stroid)
pkg_check_modules(eigen3 REQUIRED IMPORTED_TARGET eigen3)
add_library(mean_field)
target_compile_definitions(mean_field
PUBLIC
MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT=${MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT}
MEAN_FIELD_ENABLE_PROFILING=$<BOOL:${MEAN_FIELD_ENABLE_PROFILING}>
)
target_include_directories(mean_field
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libmeanfield/include>
@@ -41,10 +57,10 @@ target_include_directories(mean_field
target_sources(mean_field
PRIVATE
libmeanfield/impl/profile.cpp
libmeanfield/impl/analysis/integral.cpp
libmeanfield/impl/fem.cpp
libmeanfield/impl/mapping/coefficients.cpp
libmeanfield/impl/mapping/domain_mapper.cpp
libmeanfield/impl/mapping/compactification/kelvin.cpp
libmeanfield/impl/physics/gravity.cpp
libmeanfield/impl/physics/solid.cpp
@@ -56,8 +72,10 @@ target_sources(mean_field
libmeanfield/impl/integrators/gravity.cpp
libmeanfield/impl/integrators/mass_continuity.cpp
libmeanfield/impl/integrators/viscosity.cpp
libmeanfield/impl/mapping/domain_mapper_new.cpp
libmeanfield/impl/mapping/domain_mapper.cpp
libmeanfield/impl/mapping/transformations.cpp
libmeanfield/impl/deformation/nodal_radial_surface.cpp
libmeanfield/impl/deformation/radial_extensions.cpp
libmeanfield/impl/operators/gravity_field.cpp
libmeanfield/impl/operators/gravity_field_jacobian.cpp
libmeanfield/impl/operators/kernels/gravity_kernels.cpp
@@ -71,8 +89,33 @@ 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/seed/lane_emden.cpp
libmeanfield/impl/seed/stellar_equilibrium_projection.cpp
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
libmeanfield/impl/preconditioning/gravity_field.cpp
libmeanfield/impl/operators/prepared_mass_normalization.cpp
libmeanfield/impl/operators/prepared_angular_momentum.cpp
libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp
)
if (MEAN_FIELD_ENABLE_IPO)
include(CheckIPOSupported)
check_ipo_supported(RESULT mean_field_ipo_supported OUTPUT mean_field_ipo_error LANGUAGES CXX)
if (NOT mean_field_ipo_supported)
message(FATAL_ERROR "MEAN_FIELD_ENABLE_IPO was requested, but the compiler does not support it: ${mean_field_ipo_error}")
endif ()
set_property(TARGET mean_field PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
endif ()
target_sources(mean_field
PUBLIC
FILE_SET CXX_MODULES FILES
@@ -87,7 +130,6 @@ target_sources(mean_field
libmeanfield/interface/mapping/compactification/compactification.cppm
libmeanfield/interface/mapping/compactification/kelvin.cppm
libmeanfield/interface/mapping/compactification/options.cppm
libmeanfield/interface/physics/context.cppm
libmeanfield/interface/physics/gravity.cppm
libmeanfield/interface/physics/solid.cppm
libmeanfield/interface/utils/domain.cppm
@@ -103,6 +145,22 @@ target_sources(mean_field
libmeanfield/interface/quadrature/policy.cppm
libmeanfield/interface/quadrature/mfem.cppm
libmeanfield/interface/solver/fields.cppm
libmeanfield/interface/solver/preconditioning_diagnostics.cppm
libmeanfield/interface/preconditioning/backend.cppm
libmeanfield/interface/preconditioning/backend_implementations.cppm
libmeanfield/interface/preconditioning/gravity_field.cppm
libmeanfield/interface/preconditioning/material_surface.cppm
libmeanfield/interface/preconditioning/plan.cppm
libmeanfield/interface/preconditioning/stellar_equilibrium.cppm
libmeanfield/interface/preconditioning/stellar_structure.cppm
libmeanfield/interface/preconditioning/specification_border.cppm
libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm
libmeanfield/interface/preconditioning/preconditioning.cppm
libmeanfield/interface/normalization/plan.cppm
libmeanfield/interface/normalization/physical_riesz.cppm
libmeanfield/interface/normalization/operators.cppm
libmeanfield/interface/normalization/stellar_equilibrium.cppm
libmeanfield/interface/normalization/normalization.cppm
libmeanfield/interface/operators/gravity_field.cppm
libmeanfield/interface/operators/gravity_field_jacobian.cppm
libmeanfield/interface/operators/kernels/gravity_kernels.cppm
@@ -115,13 +173,64 @@ target_sources(mean_field
libmeanfield/interface/field/field_base.cppm
libmeanfield/interface/field/field_registry.cppm
libmeanfield/interface/field/field_mfem.cppm
libmeanfield/interface/physics/barotrope.cppm
libmeanfield/interface/operators/prepared_barotropic_closure_operator.cppm
libmeanfield/interface/operators/contexts/barotropic_closure_linearization_context.cppm
libmeanfield/interface/physics/rigid_rotation.cppm
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/dimensions/quantities.cppm
libmeanfield/interface/eos/quantities.cppm
libmeanfield/interface/eos/relations.cppm
libmeanfield/interface/eos/concepts.cppm
libmeanfield/interface/eos/evaluation.cppm
libmeanfield/interface/eos/pressure_surface.cppm
libmeanfield/interface/eos/runtime.cppm
libmeanfield/interface/eos/polytropic.cppm
libmeanfield/interface/seed/lane_emden.cppm
libmeanfield/interface/models/structure/structure_base.cppm
libmeanfield/interface/models/structure/polytropic.cppm
libmeanfield/interface/models/structure_profile.cppm
libmeanfield/interface/models/specifications.cppm
libmeanfield/interface/models/typed_stellar_model.cppm
libmeanfield/interface/models/compiled_fixed_mass.cppm
libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm
libmeanfield/interface/models/compiled_fixed_central_density.cppm
libmeanfield/interface/surface/constant.cppm
libmeanfield/interface/surface/dependencies.cppm
libmeanfield/interface/surface/compiled.cppm
libmeanfield/interface/surface/compiler.cppm
libmeanfield/interface/material/thermodynamic_equations.cppm
libmeanfield/interface/deformation/descriptors.cppm
libmeanfield/interface/deformation/surface_prescription.cppm
libmeanfield/interface/deformation/nodal_radial_surface.cppm
libmeanfield/interface/deformation/interior_extension.cppm
libmeanfield/interface/deformation/vacuum_extension.cppm
libmeanfield/interface/deformation/radial_extensions.cppm
libmeanfield/interface/deformation/domain_deformation.cppm
libmeanfield/interface/models/stellar_model.cppm
libmeanfield/interface/operators/root_manifest.cppm
libmeanfield/interface/operators/prepared_constraint.cppm
libmeanfield/interface/operators/prepared_mass_normalization.cppm
libmeanfield/interface/operators/prepared_angular_momentum.cppm
libmeanfield/interface/operators/prepared_central_density.cppm
libmeanfield/interface/operators/prepared_centering_constraint.cppm
libmeanfield/interface/operators/prepared_surface_constraint.cppm
libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm
libmeanfield/interface/operators/stellar_equilibrium_compiler.cppm
libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm
libmeanfield/interface/equilibrium/stellar_discretization.cppm
libmeanfield/interface/operators/stellar_equilibrium_problem.cppm
libmeanfield/interface/seed/stellar_equilibrium_projection.cppm
libmeanfield/interface/operators/stellar_equilibrium_system.cppm
)
@@ -132,6 +241,7 @@ target_link_libraries(mean_field
mfem
PkgConfig::stroid
)
target_link_libraries(mean_field PRIVATE PkgConfig::eigen3)
add_library(test_mod)
target_sources(test_mod
@@ -154,6 +264,9 @@ pkg_check_modules(fourdst_config REQUIRED IMPORTED_TARGET fourdst_config)
add_executable(tests
tests/test_main.cpp
tests/physics/gravity.cpp
tests/physics/dimensional_quantities.cpp
tests/seed/lane_emden.cpp
tests/seed/stellar_equilibrium_projection.cpp
tests/geometry/volume.cpp
tests/quadrature/policy.cpp
tests/integrators/centrifugal.cpp
@@ -161,6 +274,7 @@ add_executable(tests
tests/mapping/domain_mapper.cpp
tests/mapping/compactification/kelvin.cpp
tests/utils/blocks.cpp
tests/utils/profiling.cpp
tests/operators/gravity_field.cpp
tests/mapping/hdiv_mass_tensor.cpp
tests/operators/prepared_hdiv_mass.cpp
@@ -168,6 +282,13 @@ add_executable(tests
tests/operators/contexts/gravity_field_context.cpp
tests/physics/gravity_monopole_accuracy.cpp
tests/physics/barotrope.cpp
tests/physics/polytropic_eos_characterization.cpp
tests/physics/equation_of_state_type_system.cpp
tests/physics/equation_of_state_consumer_contracts.cpp
tests/physics/polytropic_eos_relations.cpp
tests/physics/equation_of_state_runtime_view.cpp
tests/material/thermodynamic_equation_compilation.cpp
tests/surface/constant_surface_compilation.cpp
tests/operators/kernels/barotropic_closure_kernels.cpp
tests/operators/prepared_barotropic_closure.cpp
tests/operators/contexts/barotropic_closure_linearization_context.cpp
@@ -180,16 +301,73 @@ 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/operators/root_manifest.cpp
tests/operators/stellar_equilibrium_compiler.cpp
tests/operators/prepared_central_density.cpp
tests/operators/prepared_central_density_stellar_equilibrium.cpp
tests/models/model_specifications.cpp
tests/models/typed_stellar_model.cpp
tests/models/physics_specification_frontend.cpp
tests/models/stellar_model.cpp
tests/operators/stellar_equilibrium_system.cpp
tests/deformation/contracts.cpp
tests/deformation/surface_scalar_dof_map.cpp
tests/deformation/nodal_radial_surface.cpp
tests/deformation/radial_extensions.cpp
tests/deformation/domain_deformation.cpp
tests/operators/prepared_mass_normalization.cpp
tests/operators/prepared_angular_momentum.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
tests/preconditioning/plan.cpp
tests/preconditioning/backends.cpp
tests/preconditioning/gravity_field.cpp
tests/preconditioning/material_surface.cpp
tests/preconditioning/stellar_structure.cpp
tests/preconditioning/specification_border.cpp
tests/extensions/fixed_magnetic_specific_energy.cpp
tests/preconditioning/equilibrium_coordinates.cpp
tests/preconditioning/stellar_equilibrium.cpp
tests/normalization/plan.cpp
tests/normalization/physical_riesz.cpp
tests/normalization/stellar_equilibrium.cpp
tests/user-api/stellar_equilibrium.cpp
tests/solver/preconditioning_diagnostics.cpp
)
target_link_libraries(tests PRIVATE mean_field test_mod Catch2::Catch2 Boost::boost)
add_executable(mpi_tests
tests/mpi/mpi_test_main.cpp
tests/mpi/distributed_execution.cpp
tests/mpi/profiling.cpp
)
target_link_libraries(mpi_tests PRIVATE mean_field test_mod Catch2::Catch2 Boost::boost)
if (MEAN_FIELD_ENABLE_IPO)
set_property(TARGET tests PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
set_property(TARGET mpi_tests PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
endif ()
add_library(experiment_mod)
target_sources(experiment_mod
PUBLIC
FILE_SET CXX_MODULES FILES
experiments/experiment_results.cppm
experiments/stellar_null_space.cppm
)
target_link_libraries(experiment_mod
PUBLIC
@@ -199,11 +377,37 @@ target_link_libraries(experiment_mod
add_executable(experiments
experiments/experiment_main.cpp
experiments/full_stellar_preconditioning.cpp
experiments/gravity_accuracy_budget.cpp
experiments/gravity_preconditioning.cpp
experiments/material_surface_preconditioning.cpp
experiments/preconditioning_diagnostics.cpp
)
target_link_libraries(experiments PRIVATE mean_field test_mod experiment_mod Catch2::Catch2 Boost::boost)
add_executable(stellar_null_space_experiments
experiments/experiment_main.cpp
experiments/rigid_motion_null_space.cpp
experiments/gravity_completed_rigid_motion.cpp
experiments/coupled_gauge_modes.cpp
)
target_link_libraries(stellar_null_space_experiments
PRIVATE
mean_field
test_mod
experiment_mod
Catch2::Catch2
Boost::boost
)
if (MEAN_FIELD_ENABLE_IPO)
foreach (mean_field_ipo_target IN ITEMS test_mod experiment_mod experiments stellar_null_space_experiments)
set_property(TARGET ${mean_field_ipo_target} PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
endforeach ()
endif ()
include (CTest)
include (Catch)
catch_discover_tests(
@@ -211,3 +415,37 @@ catch_discover_tests(
experiments
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
foreach (mean_field_mpi_ranks IN ITEMS 2 4)
add_test(
NAME mpi_${mean_field_mpi_ranks}_ranks
COMMAND
${MPIEXEC_EXECUTABLE}
${MPIEXEC_NUMPROC_FLAG} ${mean_field_mpi_ranks}
${MPIEXEC_PREFLAGS}
$<TARGET_FILE:mpi_tests>
${MPIEXEC_POSTFLAGS}
"[mpi]"
)
set_tests_properties(
mpi_${mean_field_mpi_ranks}_ranks
PROPERTIES
LABELS "mpi;distributed"
PROCESSORS ${mean_field_mpi_ranks}
RESOURCE_LOCK mean_field_mpi
TIMEOUT 180
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
endforeach ()
add_custom_target(
check_mpi
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex "mpi"
DEPENDS mpi_tests
USES_TERMINAL
)
# A deliberately separate, physics-developer-facing example. Its targets
# depend on MeanField, but none of its sources are part of the mean_field
# library or the main regression-test executable.
add_subdirectory(extension_example)

View File

@@ -128,7 +128,7 @@ BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
BreakTemplateDeclarations: MultiLine
ColumnLimit: 80
ColumnLimit: 120
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4

View File

@@ -24,7 +24,165 @@ Run only the budget and choose its output path with:
./mean_field_experiments --experiment-output gravity_budget.csv --catch2 "[accuracy]"
```
## Reduced stellar-surface conditioning experiments
`stellar_null_space_experiments` is a dedicated diagnostic executable rather
than an ordinary verification or validation test. It constructs the analytic
`n = 3` Lane-Emden seed and probes only directions representable by the reduced
surface coordinates: uniform radial homology, three translation-like radial
dipoles, an axisymmetric oblate quadrupole, and a degree-12 zonal spherical
harmonic. Tangential, rotational, stellar-interior-only, and vacuum-only mesh
motions are deliberately absent because they are generated coordinates rather
than root unknowns.
The experiment prints rank-zero progress messages while it builds the seed,
solves its gravity field, and completes each surface-mode case. The reachability
probe records the surface-to-volume lift amplification, complete root Jacobian
response by block, and centered-difference agreement at zero and half the
Keplerian angular speed. Run it with:
```text
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
--experiment-output reduced_surface_reachability.csv \
--catch2 "[null_space][surface_modes][reachability]"
```
The gravity-completed probe solves the linearized mixed gravity subsystem for
the gravity-gradient and gravity-potential variations accompanying each
reduced surface mode. It then measures the complete reduced root response:
```text
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
--experiment-output gravity_completed_surface_modes.csv \
--catch2 "[null_space][surface_modes][gravity_completed]"
```
The gravity solver prints its convergence summary, while the experiment prints
the current mode and completed-case count. This probe prepares each rotation
state only once and does not repeat the expensive nonlinear finite-difference
calculations from the reachability diagnostic.
The surface-frequency probe injects normalized zonal spherical harmonics over
a range of angular degrees. For each degree it lifts the unit surface pattern
once, samples the coefficients of
`det(I + a grad(d))` at the production geometry-inspection points, and locates
the positive and negative critical fractional amplitudes without repeatedly
rebuilding trial geometries. It also records the minimum determinant at
fractional amplitudes `1e-4`, `1e-3`, and `1e-2`:
```text
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
--experiment-output surface_frequency_limits.csv \
--catch2 "[surface_modes][frequency_limit]"
```
The coupled conditioning probe evaluates an extension-aware `n = 3` homology
direction together with the nonuniform reduced surface modes. Its density and
enthalpy tangents include the coordinate-composition terms generated by the
non-affine interior extension, and its gravity variation is completed through
the discrete mixed subsystem so the fixed-infinity exterior response is
consistent. The probe prepares the equilibrium once, reuses one restricted
gravity operator and preconditioner, and uses analytic Jacobian actions:
```text
mpirun -np 1 ./cmake-build-release-homebrew/stellar_null_space_experiments \
--experiment-output coupled_surface_conditioning.csv \
--catch2 "[null_space][surface_modes][conditioning]"
```
The CSV reports prescribed and gravity-completed responses by residual block,
the response normalized by the completed direction, lift conditioning where
applicable, and the convergence of each restricted gravity solve.
The homology mass-cancellation experiment evaluates the signed decomposition
```text
delta M = delta M_density + delta M_geometry
```
without solving gravity or preparing the complete coupled operator. The two
default-build cases provide the registered-order baseline and one uniform
spatial refinement:
```text
mpirun -np 1 ./cmake-build-release-homebrew/stellar_null_space_experiments \
--experiment-output homology_mass_h0_p0.csv \
--catch2 "[null_space][homology][mass_normalization][p_refinement]"
mpirun -np 1 ./cmake-build-release-homebrew/stellar_null_space_experiments \
--experiment-output homology_mass_h1_p0.csv \
--catch2 "[null_space][homology][mass_normalization][h_refinement]"
```
A reproducible one-level uniform polynomial refinement uses a separate build so
all registered field families and their quadrature policies see the same
compile-time order increment:
```text
cmake -S . -B cmake-build-release-homebrew-p1 -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_MAKE_PROGRAM=/opt/homebrew/bin/ninja \
-DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang \
-DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++ \
-DUMFPACK_DIR=/opt/homebrew/lib/cmake/UMFPACK \
-DXAD_DIR=/usr/local/lib/cmake/XAD \
-Dhypre_DIR=/usr/local/lib/cmake/HYPRE \
-Dmfem_DIR=/usr/local/lib/cmake/mfem \
-DBoost_DIR=/opt/homebrew/anaconda3/lib/cmake/Boost-1.82.0 \
-DMEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT=1
cmake --build cmake-build-release-homebrew-p1 \
--target stellar_null_space_experiments -j 8
mpirun -np 1 \
./cmake-build-release-homebrew-p1/stellar_null_space_experiments \
--experiment-output homology_mass_h0_p1.csv \
--catch2 "[null_space][homology][mass_normalization][p_refinement]"
```
A whole-Jacobian dense singular-value experiment is intentionally deferred.
The checked-in `sandbox.smesh` is too large for a useful dense SVD, and the
current matrix-free root operator does not provide a transpose action needed by
a scalable smallest-singular-value method.
The executable needs the same dependencies, generated module mapping, and
configuration registration as the existing Catch2 test executable. Add
`experiment_main.cpp` and `gravity_accuracy_budget.cpp` as a second executable
next to that target; do not add them to the ordinary test executable.
## P0 preconditioning baseline
The P0 diagnostic establishes the unpreconditioned reference for the complete,
central-density-closed `n = 3` stellar equilibrium Jacobian. It uses an identity
inverse preconditioner with FGMRES, recomputes the true residual independently,
records every residual block, and counts and times Jacobian and preconditioner
applications. A separate fixed-operator Arnoldi measurement acts explicitly on
the right-preconditioned product `J M^-1`. Its singular-value ratio is a
projected Krylov-space condition proxy, not the condition number of the full
Jacobian. The same output records Ritz values, clustering about one,
nonnormality, and the real extent of the projected field of values.
The extended baseline preserves the fixed 40-iteration FGMRES budget used by
the original P0 run and increases the Arnoldi dimension from 12 to 48. It writes
the complete reported FGMRES residual history, block-relative and
manifest-scaled final residuals, the fraction of the squared residual in each
physics block, timings for construction/projection/preparation/direct-residual
measurement, and separate Arnoldi operator and orthogonalization timings. Live
progress messages delimit every expensive phase and report every fourth
Arnoldi application. The CSV records whether it came from a Debug or Release
build.
Run the focused synthetic verification tests with:
```text
./cmake-build-debug-homebrew/tests "[preconditioning][diagnostics][unit]"
```
Run the performance and spectral measurement separately with:
```text
mpirun -np 1 ./cmake-build-release-homebrew/experiments \
--experiment-output preconditioning_p0_identity_extended.csv \
--catch2 "[preconditioning][diagnostics][baseline]"
```
Set `MEANFIELD_SINGLE_JACOBIAN_BENCHMARK=1` to stop after the initial prepared
Jacobian timing instead of running FGMRES and Arnoldi.

View File

@@ -0,0 +1,781 @@
#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
namespace null_space = experiment::null_space;
struct GaugeMode final {
std::string name;
std::string family;
int axis{-1};
bool requiresGravityCompletion{true};
mfem::Vector direction;
};
class GravityUnknownJacobian final : public mfem::Operator {
public:
explicit GravityUnknownJacobian(
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator
)
: mfem::Operator(
stellarOperator.GetLayout().size(null_space::gravityGradientValue) +
stellarOperator.GetLayout().size(null_space::gravityPotentialValue)
),
m_stellarOperator(stellarOperator),
m_gravityGradientSize(stellarOperator.GetLayout().size(null_space::gravityGradientValue)) {
MFEM_VERIFY(Width() == Height(), "The restricted gravity Jacobian must be square.");
}
void Mult(
const mfem::Vector &gravityDirection,
mfem::Vector &gravityAction
) const override {
MFEM_VERIFY(gravityDirection.Size() == Width(), "The restricted gravity direction has the wrong size.");
const mfem::Vector gravityGradientDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()), m_gravityGradientSize
);
const mfem::Vector gravityPotentialDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()) + m_gravityGradientSize,
Width() - m_gravityGradientSize
);
m_stellarOperator.GetGravityOperator().ApplyGravityUnknowns(
gravityGradientDirection, gravityPotentialDirection,
m_stellarOperator.GetGravityContext().GetGeometryContext(), gravityAction
);
}
[[nodiscard]] int gravity_gradient_size() const noexcept {
return m_gravityGradientSize;
}
private:
const mean_field::operators::PreparedStellarEquilibriumOperator &m_stellarOperator;
int m_gravityGradientSize;
};
struct GravityCompletionResult final {
mfem::Vector direction;
double rightHandSideNorm{0.0};
double residualNorm{0.0};
double relativeResidual{0.0};
double finalNorm{0.0};
int iterations{0};
bool solvePerformed{false};
};
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
[[nodiscard]] mfem::Vector gravity_residual_blocks(
const mfem::Vector &completeAction,
const mean_field::operators::StellarEquilibriumLayout &layout
) {
const mfem::Vector gradient =
null_space::const_residual_view(completeAction, layout, null_space::gravityGradientResidual);
const mfem::Vector potential =
null_space::const_residual_view(completeAction, layout, null_space::gravityPotentialResidual);
mfem::Vector result(gradient.Size() + potential.Size());
mfem::Vector(result.GetData(), gradient.Size()) = gradient;
mfem::Vector(result.GetData() + gradient.Size(), potential.Size()) = potential;
return result;
}
void assign_gravity_completion(
mfem::Vector &completeDirection,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mfem::Vector &gravityCompletion,
const int gravityGradientSize
) {
const mfem::Vector gravityGradient(
const_cast<mfem::real_t *>(gravityCompletion.GetData()), gravityGradientSize
);
const mfem::Vector gravityPotential(
const_cast<mfem::real_t *>(gravityCompletion.GetData()) + gravityGradientSize,
gravityCompletion.Size() - gravityGradientSize
);
null_space::assign_value_block(completeDirection, layout, null_space::gravityGradientValue, gravityGradient);
null_space::assign_value_block(completeDirection, layout, null_space::gravityPotentialValue, gravityPotential);
}
[[nodiscard]] GravityCompletionResult solve_gravity_completion(
const mfem::Vector &prescribedAction,
const mean_field::operators::StellarEquilibriumLayout &layout,
const MPI_Comm communicator,
GravityUnknownJacobian &gravityJacobian,
mfem::MINRESSolver &gravitySolver
) {
mfem::Vector rightHandSide = gravity_residual_blocks(prescribedAction, layout);
rightHandSide *= -1.0;
GravityCompletionResult result;
result.direction.SetSize(gravityJacobian.Width());
result.direction = 0.0;
result.rightHandSideNorm = null_space::global_norm(rightHandSide, communicator);
const double skipThreshold = 100.0 * std::numeric_limits<double>::epsilon();
if (result.rightHandSideNorm <= skipThreshold) {
return result;
}
gravitySolver.Mult(rightHandSide, result.direction);
REQUIRE(gravitySolver.GetConverged());
mfem::Vector action;
gravityJacobian.Mult(result.direction, action);
action -= rightHandSide;
result.residualNorm = null_space::global_norm(action, communicator);
result.relativeResidual = result.residualNorm / result.rightHandSideNorm;
result.finalNorm = gravitySolver.GetFinalNorm();
result.iterations = gravitySolver.GetNumIterations();
result.solvePerformed = true;
REQUIRE(std::isfinite(result.relativeResidual));
return result;
}
class ExtensionAwareHomologyScalarCoefficient final : public mfem::Coefficient {
public:
ExtensionAwareHomologyScalarCoefficient(
const mfem::ParGridFunction &baseField,
const mfem::ParGridFunction &coordinateVelocity,
const mfem::Vector &referenceCenter,
const double physicalScalingExponent
)
: m_baseField(&baseField),
m_coordinateVelocity(&coordinateVelocity),
m_referenceCenter(&referenceCenter),
m_physicalScalingExponent(physicalScalingExponent) {
}
double Eval(
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integrationPoint
) override {
transformation.SetIntPoint(&integrationPoint);
mfem::Vector referencePosition;
mfem::Vector coordinateVelocity;
mfem::Vector baseGradient;
transformation.Transform(integrationPoint, referencePosition);
m_coordinateVelocity->GetVectorValue(transformation, integrationPoint, coordinateVelocity);
m_baseField->GetGradient(transformation, baseGradient);
coordinateVelocity -= referencePosition;
coordinateVelocity += *m_referenceCenter;
return -m_physicalScalingExponent * m_baseField->GetValue(transformation, integrationPoint) +
baseGradient * coordinateVelocity;
}
private:
const mfem::ParGridFunction *m_baseField;
const mfem::ParGridFunction *m_coordinateVelocity;
const mfem::Vector *m_referenceCenter;
double m_physicalScalingExponent;
};
[[nodiscard]] mfem::Vector project_extension_aware_homology_scalar(
mfem::ParFiniteElementSpace &finiteElementSpace,
const mean_field::field::FieldDofMap &fieldMap,
const mfem::Vector &baseReducedField,
const mfem::ParGridFunction &coordinateVelocity,
const mfem::Vector &referenceCenter,
const double physicalScalingExponent
) {
mfem::ParGridFunction baseField(&finiteElementSpace);
baseField.SetFromTrueDofs(fieldMap.scatter(baseReducedField));
ExtensionAwareHomologyScalarCoefficient coefficient(
baseField, coordinateVelocity, referenceCenter, physicalScalingExponent
);
mfem::ParGridFunction directionField(&finiteElementSpace);
directionField.ProjectCoefficient(coefficient);
mfem::Vector directionTrue;
directionField.GetTrueDofs(directionTrue);
return fieldMap.gather(directionTrue);
}
struct HomologyMassCancellation final {
double currentMass{0.0};
double targetMass{0.0};
double densityContribution{0.0};
double geometryContribution{0.0};
double completeDerivative{0.0};
};
[[nodiscard]] HomologyMassCancellation measure_homology_mass_cancellation(
const mean_field::operators::PreparedMassNormalizationOperator &massOperator,
const mfem::Vector &densityDirection,
const mfem::Vector &volumeDirection
) {
mfem::Vector densityAction;
mfem::Vector geometryAction;
mfem::Vector completeAction;
massOperator.ApplyDensityJacobianAction(densityDirection, densityAction);
massOperator.ApplyDisplacementJacobianAction(volumeDirection, geometryAction);
massOperator.ApplyCompleteJacobianAction(densityDirection, volumeDirection, completeAction);
REQUIRE(densityAction.Size() == 1);
REQUIRE(geometryAction.Size() == 1);
REQUIRE(completeAction.Size() == 1);
const double recomposedDerivative = densityAction(0) + geometryAction(0);
const double comparisonScale = std::max({1.0, std::abs(recomposedDerivative), std::abs(completeAction(0))});
CHECK(
std::abs(completeAction(0) - recomposedDerivative) <=
64.0 * std::numeric_limits<double>::epsilon() * comparisonScale
);
return {
.currentMass = massOperator.GetCurrentMass(),
.targetMass = massOperator.GetTargetMass(),
.densityContribution = densityAction(0),
.geometryContribution = geometryAction(0),
.completeDerivative = completeAction(0)
};
}
void add_homology_mass_metrics(
std::map<
std::string,
double> &metrics,
const HomologyMassCancellation &cancellation
) {
const double uncancelledMagnitude =
std::abs(cancellation.densityContribution) + std::abs(cancellation.geometryContribution);
const double targetScale = std::max(std::abs(cancellation.targetMass), std::numeric_limits<double>::epsilon());
metrics.emplace("current_mass", cancellation.currentMass);
metrics.emplace("target_mass", cancellation.targetMass);
metrics.emplace("base_mass_residual", cancellation.currentMass - cancellation.targetMass);
metrics.emplace(
"relative_base_mass_residual", (cancellation.currentMass - cancellation.targetMass) / targetScale
);
metrics.emplace("density_mass_derivative", cancellation.densityContribution);
metrics.emplace("geometry_mass_derivative", cancellation.geometryContribution);
metrics.emplace("complete_mass_derivative", cancellation.completeDerivative);
metrics.emplace("mass_derivative_uncancelled_magnitude", uncancelledMagnitude);
metrics.emplace(
"mass_derivative_relative_cancellation_error",
std::abs(cancellation.completeDerivative) /
std::max(uncancelledMagnitude, std::numeric_limits<double>::epsilon())
);
metrics.emplace("complete_mass_derivative_per_target_mass", cancellation.completeDerivative / targetScale);
}
[[nodiscard]] GaugeMode make_homology_mode(
null_space::N3Equilibrium &fixture,
const null_space::SurfaceMode &uniformRadialMode
) {
const auto &layout = fixture.stellar_operator().GetLayout();
const auto &state = fixture.state();
mfem::Vector direction(layout.value_offsets().Last());
direction = 0.0;
const mfem::Vector volumeDirection = fixture.lifted_surface_direction(uniformRadialMode.direction);
mfem::ParGridFunction coordinateVelocity(fixture.fem().displacementFes.get());
coordinateVelocity.SetFromTrueDofs(volumeDirection);
const mean_field::field::FieldDofMap densityMap =
mean_field::field::make_field_dof_map<mean_field::field::Density, null_space::DomainSchema>(
*fixture.fem().densityFes
);
const mean_field::field::FieldDofMap enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, null_space::DomainSchema>(
*fixture.fem().enthalpyFes
);
const mfem::Vector &referenceCenter = fixture.model().surfaceDeformationPrescription().referenceCenter();
const mfem::Vector densityDirection = project_extension_aware_homology_scalar(
*fixture.fem().densityFes, densityMap,
null_space::const_value_view(state, layout, null_space::densityValue), coordinateVelocity, referenceCenter,
3.0
);
null_space::assign_value_block(direction, layout, null_space::densityValue, densityDirection);
null_space::assign_value_block(
direction, layout, null_space::surfaceDeformationValue,
null_space::const_value_view(uniformRadialMode.direction, layout, null_space::surfaceDeformationValue)
);
/*
* A physical homology scales rho and h, while the power-law mesh
* extension moves interior coordinates non-affinely. The scalar
* tangents therefore contain the coordinate-composition term
* grad(f) dot (v - (X-Xc)) in addition to their physical scaling.
* Gravity is completed through the discrete mixed subsystem below,
* which also supplies the correct fixed-infinity exterior response.
*/
const mfem::Vector enthalpyDirection = project_extension_aware_homology_scalar(
*fixture.fem().enthalpyFes, enthalpyMap,
null_space::const_value_view(state, layout, null_space::enthalpyValue), coordinateVelocity, referenceCenter,
1.0
);
null_space::assign_value_block(direction, layout, null_space::enthalpyValue, enthalpyDirection);
null_space::value_view(direction, layout, null_space::bernoulliValue)(0) =
-null_space::const_value_view(state, layout, null_space::bernoulliValue)(0);
return {
.name = "n3_homology",
.family = "homology",
.axis = -1,
.requiresGravityCompletion = true,
.direction = std::move(direction)
};
}
[[nodiscard]] std::vector<GaugeMode> make_gauge_modes(null_space::N3Equilibrium &fixture) {
auto surfaceModes = null_space::make_surface_modes(fixture);
const auto homologyMode = std::ranges::find_if(surfaceModes, [](const null_space::SurfaceMode &mode) {
return mode.kind == null_space::SurfaceModeKind::uniform_radial;
});
MFEM_VERIFY(homologyMode != surfaceModes.end(), "The reduced surface modes do not contain homology.");
std::vector<GaugeMode> modes;
modes.reserve(6);
modes.push_back(make_homology_mode(fixture, *homologyMode));
for (auto &surfaceMode : surfaceModes) {
if (surfaceMode.kind == null_space::SurfaceModeKind::uniform_radial) {
continue;
}
modes.push_back(
{.name = surfaceMode.name,
.family = null_space::surface_mode_kind_name(surfaceMode.kind),
.axis = surfaceMode.axis,
.requiresGravityCompletion = true,
.direction = std::move(surfaceMode.direction)}
);
}
return modes;
}
[[nodiscard]] long long global_nonzero_count(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
long long localCount = 0;
for (int index = 0; index < vector.Size(); ++index) {
if (vector(index) != 0.0) {
++localCount;
}
}
long long globalCount = 0;
MPI_Allreduce(&localCount, &globalCount, 1, MPI_LONG_LONG, MPI_SUM, communicator);
return globalCount;
}
[[nodiscard]] mean_field::models::structure::StructureSeed make_n3_seed(null_space::Model &model) {
constexpr double surfaceCoordinate = 6.8968486193769603755;
constexpr int radialSampleCount = 8192;
const double pi = std::acos(-1.0);
const double radius = mean_field::utils::RADIUS;
const double targetMass = mean_field::utils::MASS;
constexpr double dimensionlessMass = 2.0182359509662283534;
const double polytropicConstant =
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
const double centralDensity =
std::pow(surfaceCoordinate * std::sqrt(polytropicConstant / (pi * mean_field::utils::G)) / radius, 3.0);
return model.makeInitialSeed({.centralDensity = centralDensity, .radialSampleCount = radialSampleCount});
}
[[nodiscard]] mfem::Vector project_n3_density(
const mean_field::fem::FEM &fem,
const mean_field::models::structure::StructureSeed &seed
) {
const auto interpolate = [](const mfem::Vector &radii, const mfem::Vector &values, const double radius) {
if (radius <= radii(0)) {
return values(0);
}
const int finalIndex = radii.Size() - 1;
if (radius >= radii(finalIndex)) {
return values(finalIndex);
}
int lower = 0;
int upper = finalIndex;
while (upper - lower > 1) {
const int middle = lower + (upper - lower) / 2;
if (radii(middle) <= radius) {
lower = middle;
} else {
upper = middle;
}
}
const double fraction = (radius - radii(lower)) / (radii(upper) - radii(lower));
return (1.0 - fraction) * values(lower) + fraction * values(upper);
};
mfem::FunctionCoefficient densityCoefficient([&seed, &interpolate](const mfem::Vector &position) {
const double radius = position.Norml2();
return radius >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.density, radius);
});
mfem::ParGridFunction densityField(fem.densityFes.get());
densityField.ProjectCoefficient(densityCoefficient);
mfem::Vector densityTrue;
densityField.GetTrueDofs(densityTrue);
return densityTrue;
}
void run_homology_mass_cancellation_experiment(const int hRefinementLevel) {
REQUIRE(hRefinementLevel >= 0);
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, hRefinementLevel);
REQUIRE(fem.okay());
null_space::Model model = null_space::make_model();
const mean_field::models::structure::StructureSeed seed = make_n3_seed(model);
const mfem::Vector densityTrue = project_n3_density(fem, seed);
auto deformation = model.compileDomainDeformation(fem);
const auto &surface = deformation.surfaceDeformationPrescription();
mfem::Vector zeroSurfaceParameters(surface.parameterCount());
mfem::Vector homologySurfaceDirection(surface.parameterCount());
zeroSurfaceParameters = 0.0;
for (int parameter = 0; parameter < homologySurfaceDirection.Size(); ++parameter) {
homologySurfaceDirection(parameter) = surface.referenceRadius(parameter);
}
mfem::Vector volumeDirection(deformation.volumeDisplacementSize());
deformation.applyJacobian(zeroSurfaceParameters, homologySurfaceDirection, volumeDirection);
mfem::ParGridFunction coordinateVelocity(fem.displacementFes.get());
coordinateVelocity.SetFromTrueDofs(volumeDirection);
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
fem, *fem.domainMapperStateless
);
const mean_field::field::FieldDofMap &densityMap = gravityContext.GetDensityMap();
const mfem::Vector reducedDensity = densityMap.gather(densityTrue);
const mfem::Vector densityDirection = project_extension_aware_homology_scalar(
*fem.densityFes, densityMap, reducedDensity, coordinateVelocity, surface.referenceCenter(), 3.0
);
mfem::Vector zeroDisplacement(fem.displacementFes->GetTrueVSize());
mfem::Vector zeroGravityGradient(fem.gravityFluxFes->GetTrueVSize());
mfem::Vector zeroGravityPotential(fem.gravityPotentialFes->GetTrueVSize());
zeroDisplacement = 0.0;
zeroGravityGradient = 0.0;
zeroGravityPotential = 0.0;
const mean_field::operators::MassNormalizationDependencies dependencies{
.discretization = {.identity = 9101, .revision = 1},
.density = {.identity = 9103, .revision = 1},
.displacement = {.identity = 9109, .revision = 1},
.targetMass = {.identity = 9127, .revision = 1}
};
gravityContext.Prepare(
{.density = reducedDensity,
.displacement = gravityContext.GetDisplacementMap().gather(zeroDisplacement),
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(zeroGravityGradient),
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(zeroGravityPotential)},
{.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = dependencies.displacement.revision},
.density = {.value = dependencies.density.revision},
.gravity_gradient = {.value = 1},
.gravity_potential = {.value = 1}}
);
mean_field::operators::PreparedMassNormalizationOperator massOperator(
fem, *fem.domainMapperStateless, gravityContext
);
massOperator.Prepare({.targetMass = mean_field::utils::MASS}, dependencies);
const mfem::Vector reducedVolumeDirection = gravityContext.GetDisplacementMap().gather(volumeDirection);
const HomologyMassCancellation cancellation =
measure_homology_mass_cancellation(massOperator, densityDirection, reducedVolumeDirection);
std::map<std::string, double> metrics{
{"density_direction_norm", null_space::global_norm(densityDirection, fem.mesh->GetComm())},
{"surface_direction_norm", null_space::global_norm(homologySurfaceDirection, fem.mesh->GetComm())},
{"volume_direction_norm", null_space::global_norm(volumeDirection, fem.mesh->GetComm())},
{"global_element_count", static_cast<double>(fem.mesh->GetGlobalNE())},
{"global_density_true_dof_count", static_cast<double>(fem.densityFes->GlobalTrueVSize())},
{"global_displacement_true_dof_count", static_cast<double>(fem.displacementFes->GlobalTrueVSize())},
{"global_surface_parameter_count", static_cast<double>(surface.globalParameterCount())}
};
add_homology_mass_metrics(metrics, cancellation);
int rank = 0;
MPI_Comm_rank(fem.mesh->GetComm(), &rank);
if (rank == 0) {
const int pRefinementLevel = mean_field::field::uniformPolynomialOrderIncrement;
experiment::record_experiment_result(
"n3_homology_mass_cancellation",
"h" + std::to_string(hRefinementLevel) + "_p" + std::to_string(pRefinementLevel),
{{"h_refinement_level", std::to_string(hRefinementLevel)},
{"p_refinement_level", std::to_string(pRefinementLevel)},
{"density_polynomial_order", std::to_string(mean_field::field::Density::Scalar::familyOrder)},
{"enthalpy_polynomial_order", std::to_string(mean_field::field::Enthalpy::Scalar::familyOrder)},
{"displacement_polynomial_order",
std::to_string(mean_field::field::Displacement::Vector::familyOrder)},
{"gravity_polynomial_order", std::to_string(mean_field::field::Gravity::Potential::familyOrder)},
{"mesh_file", test_utils::setup_args().mesh_file}},
std::move(metrics)
);
}
}
} // namespace
TEST_CASE(
"Coupled Stellar Equilibrium Homology And Reduced Surface Mode Responses",
"[null_space][surface_modes][conditioning][homology]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = std::min(args.p.rtol, 1.0e-12);
args.p.atol = std::min(args.p.atol, 1.0e-13);
args.p.max_iters = std::max(args.p.max_iters, 1500);
null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const std::vector<GaugeMode> modes = make_gauge_modes(fixture);
const auto &stellarOperator = fixture.stellar_operator();
const auto &layout = stellarOperator.GetLayout();
GravityUnknownJacobian gravityJacobian(stellarOperator);
mean_field::operators::ReducedGravityFieldPreconditioner gravityPreconditioner(
fixture.fem(), stellarOperator.GetGravityContext().GetGeometryContext()
);
mfem::MINRESSolver gravitySolver(communicator);
gravitySolver.SetOperator(gravityJacobian);
gravitySolver.SetPreconditioner(gravityPreconditioner);
gravitySolver.SetRelTol(1.0e-10);
gravitySolver.SetAbsTol(1.0e-12);
gravitySolver.SetMaxIter(1500);
gravitySolver.SetPrintLevel(0);
for (std::size_t modeIndex = 0; modeIndex < modes.size(); ++modeIndex) {
const GaugeMode &mode = modes[modeIndex];
null_space::report_progress(
communicator,
"evaluating " + mode.name + " (" + std::to_string(modeIndex + 1) + "/" + std::to_string(modes.size()) + ")"
);
const double prescribedInputNorm = null_space::global_norm(mode.direction, communicator);
REQUIRE(std::isfinite(prescribedInputNorm));
REQUIRE(prescribedInputNorm > 0.0);
const mfem::Vector prescribedAction = fixture.jacobian_action(mode.direction);
const double prescribedActionNorm = null_space::global_norm(prescribedAction, communicator);
GravityCompletionResult completion;
completion.direction.SetSize(gravityJacobian.Width());
completion.direction = 0.0;
mfem::Vector completedDirection(mode.direction);
if (mode.requiresGravityCompletion) {
completion =
solve_gravity_completion(prescribedAction, layout, communicator, gravityJacobian, gravitySolver);
assign_gravity_completion(
completedDirection, layout, completion.direction, gravityJacobian.gravity_gradient_size()
);
}
const mfem::Vector completedAction = fixture.jacobian_action(completedDirection);
const double completedInputNorm = null_space::global_norm(completedDirection, communicator);
const double completedActionNorm = null_space::global_norm(completedAction, communicator);
const double completionNorm = null_space::global_norm(completion.direction, communicator);
REQUIRE(std::isfinite(prescribedActionNorm));
REQUIRE(std::isfinite(completedInputNorm));
REQUIRE(std::isfinite(completedActionNorm));
REQUIRE(completedInputNorm > 0.0);
std::map<std::string, double> metrics{
{"prescribed_input_norm", prescribedInputNorm},
{"prescribed_action_norm", prescribedActionNorm},
{"completed_input_norm", completedInputNorm},
{"completed_action_norm", completedActionNorm},
{"normalized_completed_response", completedActionNorm / completedInputNorm},
{"gravity_completion_norm", completionNorm},
{"gravity_solve_rhs_norm", completion.rightHandSideNorm},
{"gravity_solve_residual_norm", completion.residualNorm},
{"gravity_solve_relative_residual", completion.relativeResidual},
{"gravity_solve_final_norm", completion.finalNorm},
{"gravity_solve_iterations", static_cast<double>(completion.iterations)},
{"global_nonzero_input_dofs", static_cast<double>(global_nonzero_count(mode.direction, communicator))}
};
if (mode.family == "homology") {
const mfem::Vector densityDirection =
null_space::const_value_view(mode.direction, layout, null_space::densityValue);
const mfem::Vector volumeDirection = fixture.lifted_surface_direction(mode.direction);
const HomologyMassCancellation massCancellation = measure_homology_mass_cancellation(
stellarOperator.GetMassNormalizationOperator(), densityDirection, volumeDirection
);
add_homology_mass_metrics(metrics, massCancellation);
}
if (completedActionNorm > 0.0) {
metrics.emplace("gravity_completion_reduction", prescribedActionNorm / completedActionNorm);
}
add_block_metrics(
metrics, "prescribed_", null_space::residual_block_norms(prescribedAction, layout, communicator)
);
add_block_metrics(
metrics, "completed_", null_space::residual_block_norms(completedAction, layout, communicator)
);
if (rank == 0) {
experiment::record_experiment_result(
"coupled_reduced_surface_mode_conditioning", mode.name,
{{"mode_family", mode.family},
{"axis", std::to_string(mode.axis)},
{"gravity_completion_requested", mode.requiresGravityCompletion ? "true" : "false"},
{"gravity_solve_performed", completion.solvePerformed ? "true" : "false"},
{"rotation_fraction_of_keplerian", "0.0"},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(stellarOperator.Width())}},
std::move(metrics)
);
}
}
null_space::report_progress(communicator, "coupled reduced surface-mode probe complete; writing CSV output");
}
TEST_CASE(
"Fixed Central Density Phase Couples To The N3 Homology Tangent",
"[null_space][homology][central_density][phase]"
) {
mean_field::utils::Args args = test_utils::setup_args();
null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
const std::vector<GaugeMode> modes = make_gauge_modes(fixture);
const auto homology = std::ranges::find_if(modes, [](const GaugeMode &mode) { return mode.family == "homology"; });
REQUIRE(homology != modes.end());
const auto &layout = fixture.stellar_operator().GetLayout();
const mfem::Vector enthalpy = null_space::const_value_view(fixture.state(), layout, null_space::enthalpyValue);
const mfem::Vector enthalpyDirection =
null_space::const_value_view(homology->direction, layout, null_space::enthalpyValue);
const mean_field::field::FieldDofMap enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, null_space::DomainSchema>(
*fixture.fem().enthalpyFes
);
mfem::Vector origin(fixture.fem().mesh->SpaceDimension());
origin = 0.0;
mean_field::field::FieldPointDofMap centerDof =
mean_field::field::make_field_point_dof_map<mean_field::field::Enthalpy>(
*fixture.fem().enthalpyFes, enthalpyMap, origin, 1.0e-12
);
double localCentralEnthalpy = 0.0;
for (const int reducedDof : centerDof.reduced_dofs()) {
localCentralEnthalpy += enthalpy(reducedDof);
}
double centralEnthalpy = 0.0;
MPI_Allreduce(&localCentralEnthalpy, &centralEnthalpy, 1, MPI_DOUBLE, MPI_SUM, communicator);
REQUIRE(std::isfinite(centralEnthalpy));
REQUIRE(centralEnthalpy > 0.0);
const auto &equationOfState = fixture.model().equationOfState();
const mean_field::eos::DensityValue targetDensity = mean_field::eos::evaluate<mean_field::eos::quantity::Density>(
equationOfState, mean_field::eos::SpecificEnthalpyValue{centralEnthalpy}
);
const mean_field::models::CompiledFixedCentralDensity compiled =
mean_field::models::compileConstraint(mean_field::models::FixedCentralDensity{targetDensity}, equationOfState);
mean_field::operators::PreparedCentralDensityConstraint phase(std::move(centerDof), communicator);
phase.Prepare(compiled, enthalpy, 0.0, {.enthalpy = {.identity = 3251, .revision = 1}});
mfem::Vector enthalpyAction(enthalpy.Size());
mfem::Vector phaseAction(1);
enthalpyAction = 0.0;
phaseAction = 0.0;
phase.ApplyJacobian(
{.enthalpyVariation = enthalpyDirection, .borderVariation = 0.0},
{.enthalpyAction = enthalpyAction, .phaseAction = phaseAction}
);
const double couplingScale = std::max(1.0, std::abs(compiled.targetEnthalpy().value()));
const double enthalpyDirectionNorm = null_space::global_norm(enthalpyDirection, communicator);
const double homologyDirectionNorm = null_space::global_norm(homology->direction, communicator);
const double absolutePhaseCoupling = std::abs(phaseAction(0));
INFO("Central enthalpy = " << centralEnthalpy);
INFO("N3 homology phase coupling = " << phaseAction(0));
REQUIRE(std::isfinite(phaseAction(0)));
REQUIRE(enthalpyDirectionNorm > 0.0);
REQUIRE(homologyDirectionNorm > 0.0);
CHECK(absolutePhaseCoupling > 100.0 * std::numeric_limits<double>::epsilon() * couplingScale);
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
experiment::record_experiment_result(
"fixed_central_density_homology_coupling", "n3_homology",
{{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
{{"target_density", compiled.targetDensity().value()},
{"target_enthalpy", compiled.targetEnthalpy().value()},
{"central_enthalpy", centralEnthalpy},
{"homology_phase_action", phaseAction(0)},
{"absolute_phase_coupling", absolutePhaseCoupling},
{"target_scaled_phase_coupling", absolutePhaseCoupling / couplingScale},
{"enthalpy_direction_norm", enthalpyDirectionNorm},
{"enthalpy_normalized_phase_coupling", absolutePhaseCoupling / enthalpyDirectionNorm},
{"homology_direction_norm", homologyDirectionNorm},
{"state_normalized_phase_coupling", absolutePhaseCoupling / homologyDirectionNorm}}
);
}
}
TEST_CASE(
"N3 Homology Mass Cancellation At The Registered Polynomial Order",
"[null_space][homology][mass_normalization][convergence][p_refinement]"
) {
run_homology_mass_cancellation_experiment(0);
}
TEST_CASE(
"N3 Homology Mass Cancellation Under Uniform Spatial Refinement",
"[null_space][homology][mass_normalization][convergence][h_refinement]"
) {
run_homology_mass_cancellation_experiment(1);
}

View File

@@ -6,6 +6,7 @@
#include <fourdst/config/config.h>
#include <mfem.hpp>
#include <catch2/catch_test_case_info.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
@@ -14,8 +15,6 @@
#include <string>
#include <string_view>
#include <vector>
#include <catch2/catch_test_case_info.hpp>
import mean_field;
import test_helpers;
@@ -23,7 +22,7 @@ import experiment;
using namespace experiment;
static std::string escape_csv(const std::string& value) {
static std::string escape_csv(const std::string &value) {
if (value.find_first_of(",\"\n") == std::string::npos) {
return value;
}
@@ -51,11 +50,11 @@ static void write_experiment_csv() {
std::set<std::string> parameter_names;
std::set<std::string> metric_names;
for (const ExperimentResult& result : results) {
for (const auto& [name, value] : result.parameters) {
for (const ExperimentResult &result : results) {
for (const auto &[name, value] : result.parameters) {
parameter_names.insert(name);
}
for (const auto& [name, value] : result.metrics) {
for (const auto &[name, value] : result.metrics) {
metric_names.insert(name);
}
}
@@ -68,22 +67,22 @@ static void write_experiment_csv() {
}
output << "experiment,case";
for (const std::string& name : parameter_names) {
for (const std::string &name : parameter_names) {
output << ',' << escape_csv(name);
}
for (const std::string& name : metric_names) {
for (const std::string &name : metric_names) {
output << ',' << escape_csv(name);
}
output << '\n';
output << std::setprecision(17);
for (const ExperimentResult& result : results) {
for (const ExperimentResult &result : results) {
output << escape_csv(result.experiment_name) << ',' << escape_csv(result.case_name);
for (const std::string& name : parameter_names) {
for (const std::string &name : parameter_names) {
const auto iterator = result.parameters.find(name);
output << ',' << (iterator == result.parameters.end() ? "" : escape_csv(iterator->second));
}
for (const std::string& name : metric_names) {
for (const std::string &name : metric_names) {
const auto iterator = result.metrics.find(name);
output << ',';
if (iterator != result.metrics.end()) {
@@ -104,24 +103,28 @@ public:
return "Compact console reporter that writes structured experiment measurements to CSV.";
}
void testCaseEnded(const Catch::TestCaseStats& statistics) override {
void testCaseEnded(const Catch::TestCaseStats &statistics) override {
StreamingReporterBase::testCaseEnded(statistics);
const bool passed = statistics.totals.assertions.allPassed();
std::cout << (passed ? "PASS " : "FAIL ")
<< statistics.testInfo->name
<< " (" << statistics.totals.assertions.passed
<< " assertions)\n";
std::cout << (passed ? "PASS " : "FAIL ") << statistics.testInfo->name << " ("
<< statistics.totals.assertions.passed << " assertions)\n";
}
void testRunEnded(const Catch::TestRunStats& statistics) override {
void testRunEnded(const Catch::TestRunStats &statistics) override {
StreamingReporterBase::testRunEnded(statistics);
write_experiment_csv();
}
};
CATCH_REGISTER_REPORTER("experiment", ExperimentReporter)
CATCH_REGISTER_REPORTER(
"experiment",
ExperimentReporter
)
int main(int argc, char* argv[]) {
int main(
int argc,
char *argv[]
) {
fourdst::config::Config<mean_field::utils::Args> config;
CLI::App app{"Mean Field accuracy experiments"};
@@ -149,39 +152,39 @@ int main(int argc, char* argv[]) {
}
}
std::vector<const char*> configuration_argv;
std::vector<const char *> configuration_argv;
configuration_argv.reserve(configuration_arguments.size());
for (const std::string& argument : configuration_arguments) {
for (const std::string &argument : configuration_arguments) {
configuration_argv.push_back(argument.c_str());
}
try {
app.parse(static_cast<int>(configuration_argv.size()), configuration_argv.data());
} catch (const CLI::ParseError& error) {
} catch (const CLI::ParseError &error) {
return app.exit(error);
}
std::vector<std::string> catch_arguments{argv[0]};
for (const std::string& argument : app.remaining()) {
for (const std::string &argument : app.remaining()) {
catch_arguments.push_back(argument);
}
for (const std::string& argument : catch_arguments_from_command_line) {
for (const std::string &argument : catch_arguments_from_command_line) {
catch_arguments.push_back(argument);
}
bool has_reporter = false;
for (const std::string& argument : catch_arguments) {
has_reporter = has_reporter || argument == "-r" || argument == "--reporter" ||
argument.starts_with("-r=") || argument.starts_with("--reporter=");
for (const std::string &argument : catch_arguments) {
has_reporter = has_reporter || argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") ||
argument.starts_with("--reporter=");
}
if (!has_reporter) {
catch_arguments.emplace_back("--reporter");
catch_arguments.emplace_back("experiment");
}
std::vector<const char*> catch_argv;
std::vector<const char *> catch_argv;
catch_argv.reserve(catch_arguments.size());
for (const std::string& argument : catch_arguments) {
for (const std::string &argument : catch_arguments) {
catch_argv.push_back(argument.c_str());
}

View File

@@ -18,7 +18,7 @@ export namespace experiment {
class ExperimentRegistry {
public:
static ExperimentRegistry& instance() {
static ExperimentRegistry &instance() {
static ExperimentRegistry registry;
return registry;
}
@@ -50,16 +50,20 @@ export namespace experiment {
};
inline void record_experiment_result(
const std::string& experiment_name,
const std::string& case_name,
std::map<std::string, std::string> parameters,
std::map<std::string, double> metrics
const std::string &experiment_name,
const std::string &case_name,
std::map<
std::string,
std::string> parameters,
std::map<
std::string,
double> metrics
) {
ExperimentRegistry::instance().add_result({
.experiment_name = experiment_name,
ExperimentRegistry::instance().add_result(
{.experiment_name = experiment_name,
.case_name = case_name,
.parameters = std::move(parameters),
.metrics = std::move(metrics)
});
.metrics = std::move(metrics)}
);
}
}
} // namespace experiment

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,6 @@
#include <mfem.hpp>
#include <mpi.h>
import mean_field;
import test_helpers;
import experiment;
@@ -35,50 +34,71 @@ struct AccuracyBudgetMetrics {
double virial_consistency_error{0.0};
};
static double global_norm(const mfem::Vector& vector, MPI_Comm communicator) {
static double global_norm(
const mfem::Vector &vector,
MPI_Comm communicator
) {
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);
return std::sqrt(global_norm_squared);
}
static double global_dot(const mfem::Vector& left, const mfem::Vector& right, MPI_Comm communicator) {
static double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
MPI_Comm communicator
) {
const double local_dot = left * right;
double global_dot_product = 0.0;
MPI_Allreduce(&local_dot, &global_dot_product, 1, MPI_DOUBLE, MPI_SUM, communicator);
return global_dot_product;
}
static void zero_vacuum_density(const mean_field::fem::FEM& fem, mfem::GridFunction& density) {
for (int index = 0; index < fem.vacuum_tdof_rho.Size(); ++index) {
density(fem.vacuum_tdof_rho[index]) = 0.0;
}
static void zero_vacuum_density(
const mean_field::fem::FEM &fem,
mfem::GridFunction &density
) {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
const mean_field::field::FieldDofMap density_map =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*fem.densityFes);
mfem::Vector density_true;
density.GetTrueDofs(density_true);
const mfem::Vector supported_density = density_map.gather(density_true);
density_map.scatter(supported_density, density_true);
density.SetFromTrueDofs(density_true);
}
static int diagnostic_quadrature_order(const mean_field::fem::FEM& fem) {
return 2 * std::max(fem.L2_fes->GetMaxElementOrder(), fem.RT_fes->GetMaxElementOrder()) + 8;
static int diagnostic_quadrature_order(const mean_field::fem::FEM &fem) {
return 2 * std::max(fem.gravityPotentialFes->GetMaxElementOrder(), fem.gravityFluxFes->GetMaxElementOrder()) + 8;
}
static mfem::Vector assemble_monopole_projection_rhs(
mean_field::fem::FEM& fem,
const mfem::GridFunction& displacement,
mean_field::fem::FEM &fem,
const mfem::GridFunction &displacement,
const double mass,
const double stellar_radius
) {
static_cast<void>(displacement);
*fem.displacement = displacement;
mfem::Vector local_rhs(fem.RT_fes->GetVSize());
mfem::Vector local_rhs(fem.gravityFluxFes->GetVSize());
local_rhs = 0.0;
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
const int vacuum_attribute = field_dof_test_utils::vacuum_material_attribute;
const int quadrature_order = diagnostic_quadrature_order(fem);
mean_field::mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int element_id = 0; element_id < fem.mesh->GetNE(); ++element_id) {
const mfem::FiniteElement& gravity_element = *fem.RT_fes->GetFE(element_id);
mfem::ElementTransformation* transformation = fem.mesh->GetElementTransformation(element_id);
const mfem::FiniteElement &gravity_element = *fem.gravityFluxFes->GetFE(element_id);
mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element_id);
mfem::Array<int> gravity_dofs;
mfem::DofTransformation* gravity_transform = fem.RT_fes->GetElementVDofs(element_id, gravity_dofs);
mfem::DofTransformation *gravity_transform = fem.gravityFluxFes->GetElementVDofs(element_id, gravity_dofs);
const int dof_count = gravity_element.GetDof();
const int dimension = transformation->GetSpaceDim();
@@ -86,18 +106,20 @@ static mfem::Vector assemble_monopole_projection_rhs(
mfem::Vector physical_position(dimension);
mfem::Vector analytic_field(dimension);
mfem::Vector pulled_field(dimension);
mfem::DenseMatrix mapping_jacobian(dimension);
mfem::DenseMatrix vector_shape(dof_count, dimension);
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 quadrature_point_id = 0; quadrature_point_id < rule.GetNPoints(); ++quadrature_point_id) {
const mfem::IntegrationPoint& point = rule.IntPoint(quadrature_point_id);
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
const mfem::IntegrationPoint &point = rule.IntPoint(quadrature_point_id);
mean_field::mapping::MappingPointContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, point, mapping_context) ==
mean_field::mapping::MappingStatus::valid,
"Invalid mapping in monopole projection RHS."
);
physical_position = mapping_context.physical_position;
const double radius = physical_position.Norml2();
MFEM_VERIFY(std::isfinite(radius) && radius > 0.0, "Invalid radius in monopole projection RHS.");
@@ -106,12 +128,10 @@ static mfem::Vector assemble_monopole_projection_rhs(
if (transformation->Attribute == vacuum_attribute) {
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);
}
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
mapping_jacobian.MultTranspose(analytic_field, pulled_field);
mapping_context.mapping_jacobian.MultTranspose(analytic_field, pulled_field);
transformation->SetIntPoint(&point);
gravity_element.CalcVShape(*transformation, vector_shape);
@@ -130,9 +150,9 @@ static mfem::Vector assemble_monopole_projection_rhs(
local_rhs.AddElementVector(gravity_dofs, element_rhs);
}
mfem::Vector true_rhs(fem.RT_fes->GetTrueVSize());
mfem::Vector true_rhs(fem.gravityFluxFes->GetTrueVSize());
true_rhs = 0.0;
const mfem::Operator* prolongation = fem.RT_fes->GetProlongationMatrix();
const mfem::Operator *prolongation = fem.gravityFluxFes->GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_rhs, true_rhs);
} else {
@@ -143,112 +163,111 @@ static mfem::Vector assemble_monopole_projection_rhs(
}
static mfem::Vector project_monopole_gradient(
mean_field::fem::FEM& fem,
const mfem::GridFunction& displacement,
mean_field::fem::FEM &fem,
const mfem::GridFunction &displacement,
const double mass,
const double stellar_radius
) {
mfem::Vector displacement_true;
displacement.GetTrueDofs(displacement_true);
const mfem::Vector projection_rhs = assemble_monopole_projection_rhs(
fem,
displacement,
mass,
stellar_radius
);
const mfem::Vector projection_rhs_true = assemble_monopole_projection_rhs(fem, displacement, mass, stellar_radius);
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(
fem,
*fem.domain_mapper_stateless
);
mass_operator.Prepare(displacement_true);
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(fem, *fem.domainMapperStateless);
mass_operator.Prepare(mass_operator.GetDisplacementMap().gather(displacement_true));
mfem::CGSolver solver(fem.RT_fes->GetComm());
const mfem::Vector projection_rhs = mass_operator.GetFluxMap().gather(projection_rhs_true);
mfem::CGSolver solver(fem.gravityFluxFes->GetComm());
solver.SetOperator(mass_operator);
solver.SetRelTol(1.0e-11);
solver.SetAbsTol(1.0e-13);
solver.SetMaxIter(4000);
solver.SetPrintLevel(0);
mfem::Vector projected_gradient(fem.RT_fes->GetTrueVSize());
projected_gradient = 0.0;
solver.Mult(projection_rhs, projected_gradient);
mfem::Vector projected_gradient_reduced(mass_operator.GetFluxMap().reduced_size());
projected_gradient_reduced = 0.0;
solver.Mult(projection_rhs, projected_gradient_reduced);
mfem::Vector residual;
mass_operator.Mult(projected_gradient, residual);
mass_operator.Mult(projected_gradient_reduced, residual);
residual -= projection_rhs;
const double relative_residual = global_norm(residual, fem.RT_fes->GetComm()) /
std::max(global_norm(projection_rhs, fem.RT_fes->GetComm()), std::numeric_limits<double>::epsilon());
const double relative_residual =
global_norm(residual, fem.gravityFluxFes->GetComm()) /
std::max(global_norm(projection_rhs, fem.gravityFluxFes->GetComm()), std::numeric_limits<double>::epsilon());
REQUIRE(std::isfinite(relative_residual));
REQUIRE(relative_residual < 1.0e-8);
return projected_gradient;
return mass_operator.GetFluxMap().scatter(projected_gradient_reduced);
}
static double mapped_hdiv_relative_gap(
mean_field::fem::FEM& fem,
const mfem::GridFunction& displacement,
const mfem::Vector& calculated,
const mfem::Vector& reference
mean_field::fem::FEM &fem,
const mfem::GridFunction &displacement,
const mfem::Vector &calculated,
const mfem::Vector &reference
) {
mfem::Vector displacement_true;
displacement.GetTrueDofs(displacement_true);
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(
fem,
*fem.domain_mapper_stateless
);
mass_operator.Prepare(displacement_true);
mean_field::operators::PreparedMappedHDivMassOperator mass_operator(fem, *fem.domainMapperStateless);
mass_operator.Prepare(mass_operator.GetDisplacementMap().gather(displacement_true));
mfem::Vector difference(calculated);
difference -= reference;
mfem::Vector difference_action;
mfem::Vector reference_action;
mass_operator.Mult(difference, difference_action);
mass_operator.Mult(reference, reference_action);
const mfem::Vector reduced_difference = mass_operator.GetFluxMap().gather(difference);
const mfem::Vector reduced_reference = mass_operator.GetFluxMap().gather(reference);
mass_operator.Mult(reduced_difference, difference_action);
mass_operator.Mult(reduced_reference, reference_action);
const double difference_energy = global_dot(difference, difference_action, fem.RT_fes->GetComm());
const double reference_energy = global_dot(reference, reference_action, fem.RT_fes->GetComm());
const double difference_energy = global_dot(reduced_difference, difference_action, fem.gravityFluxFes->GetComm());
const double reference_energy = global_dot(reduced_reference, reference_action, fem.gravityFluxFes->GetComm());
MFEM_VERIFY(reference_energy > 0.0, "Projected monopole field has zero mapped H(div) norm.");
return std::sqrt(std::max(0.0, difference_energy) / reference_energy);
}
static AccuracyBudgetEnergies measure_stellar_energies(
mean_field::fem::FEM& fem,
const mfem::GridFunction& density,
const mean_field::physics::GravitySolution& solution
mean_field::fem::FEM &fem,
const mfem::GridFunction &density,
const mean_field::physics::GravitySolution &solution
) {
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
const int vacuum_attribute = field_dof_test_utils::vacuum_material_attribute;
const int quadrature_order = diagnostic_quadrature_order(fem);
mean_field::mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
double local_binding = 0.0;
double local_virial = 0.0;
mfem::Vector physical_position(3);
mfem::Vector reference_field(3);
mfem::Vector physical_field(3);
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);
const mfem::IntegrationPoint &point = rule.IntPoint(quadrature_point_id);
transformation->SetIntPoint(&point);
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
const double mapping_determinant = mapping_jacobian.Det();
mean_field::mapping::MappingPointContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, point, mapping_context) ==
mean_field::mapping::MappingStatus::valid,
"Invalid mapping in energy diagnostic."
);
physical_position = mapping_context.physical_position;
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping_jacobian;
const double mapping_determinant = mapping_context.mapping_determinant;
MFEM_VERIFY(mapping_determinant > 0.0, "Non-positive mapping determinant in energy diagnostic.");
solution.gradPhi.GetVectorValue(element_id, point, reference_field);
@@ -264,16 +283,16 @@ static AccuracyBudgetEnergies measure_stellar_energies(
}
AccuracyBudgetEnergies energies;
MPI_Allreduce(&local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm());
MPI_Allreduce(&local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm());
MPI_Allreduce(&local_binding, &energies.binding, 1, MPI_DOUBLE, MPI_SUM, fem.densityFes->GetComm());
MPI_Allreduce(&local_virial, &energies.virial, 1, MPI_DOUBLE, MPI_SUM, fem.densityFes->GetComm());
return energies;
}
static double reduced_gravity_relative_residual(
mean_field::fem::FEM& fem,
const mfem::GridFunction& density,
const mfem::GridFunction& displacement,
const mean_field::physics::GravitySolution& solution
mean_field::fem::FEM &fem,
const mfem::GridFunction &density,
const mfem::GridFunction &displacement,
const mean_field::physics::GravitySolution &solution
) {
using GravityFieldForm = mean_field::utils::blocks::gravity_field_form;
@@ -284,12 +303,22 @@ static double reduced_gravity_relative_residual(
mean_field::utils::blocks::gravity_field.poisson_term
);
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
const mean_field::field::FieldDofMap density_map =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*fem.densityFes);
const mean_field::field::FieldDofMap displacement_map =
mean_field::field::make_field_dof_map<mean_field::field::Displacement, DomainSchema>(*fem.displacementFes);
const mean_field::field::FieldDofMap gravity_flux_map =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*fem.gravityFluxFes);
const mean_field::field::FieldDofMap gravity_potential_map =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*fem.gravityPotentialFes);
const std::array<int, GravityFieldForm::value_block_count> value_sizes{
fem.L2_fes->GetTrueVSize(), fem.Vec_H1_fes->GetTrueVSize(),
fem.RT_fes->GetTrueVSize(), fem.L2_fes->GetTrueVSize()
density_map.reduced_size(), displacement_map.reduced_size(), gravity_flux_map.reduced_size(),
gravity_potential_map.reduced_size()
};
const std::array<int, GravityFieldForm::residual_block_count> residual_sizes{
fem.RT_fes->GetTrueVSize(), fem.L2_fes->GetTrueVSize()
gravity_flux_map.reduced_size(), gravity_potential_map.reduced_size()
};
const mean_field::utils::blocks::form_layout<GravityFieldForm> layout(value_sizes, residual_sizes);
@@ -303,56 +332,44 @@ static double reduced_gravity_relative_residual(
solution.phi.GetTrueDofs(potential_true);
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
fem,
*fem.domain_mapper_stateless
fem, *fem.domainMapperStateless
);
mean_field::operators::GravityFieldJacobianOperator jacobian(
fem,
*fem.domain_mapper_stateless,
linearization_context,
layout.value_offsets(),
layout.residual_offsets()
fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
);
mean_field::operators::GravityFieldOperator field_operator(
fem,
*fem.domain_mapper_stateless,
linearization_context,
layout.value_offsets(),
jacobian
fem, *fem.domainMapperStateless, linearization_context, layout.value_offsets(), jacobian
);
mean_field::operators::context::gravity_field::GravityFieldGeometryContext geometry_context(
fem,
*fem.domain_mapper_stateless
fem, *fem.domainMapperStateless
);
mean_field::operators::ReducedGravityFieldOperator reduced_operator(
field_operator,
geometry_context,
displacement_true
field_operator, geometry_context, displacement_map.gather(displacement_true)
);
mfem::Vector right_hand_side;
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
reduced_operator.BuildRightHandSide(density_map.gather(density_true), right_hand_side);
mfem::BlockVector state(layout.residual_offsets());
state = 0.0;
state.GetBlock(gradient_block) = gradient_true;
state.GetBlock(poisson_block) = potential_true;
state.GetBlock(gradient_block) = gravity_flux_map.gather(gradient_true);
state.GetBlock(poisson_block) = gravity_potential_map.gather(potential_true);
mfem::Vector residual;
reduced_operator.Mult(state, residual);
residual -= right_hand_side;
return global_norm(residual, fem.L2_fes->GetComm()) /
std::max(global_norm(right_hand_side, fem.L2_fes->GetComm()), std::numeric_limits<double>::epsilon());
return global_norm(residual, fem.mesh->GetComm()) /
std::max(global_norm(right_hand_side, fem.mesh->GetComm()), std::numeric_limits<double>::epsilon());
}
static AccuracyBudgetMetrics measure_monopole_accuracy(
mean_field::fem::FEM& fem,
const mfem::GridFunction& density,
const mfem::GridFunction& displacement,
const mean_field::physics::GravitySolution& solution,
const mfem::ParGridFunction& projected_potential,
const mfem::Vector& projected_gradient,
mean_field::fem::FEM &fem,
const mfem::GridFunction &density,
const mfem::GridFunction &displacement,
const mean_field::physics::GravitySolution &solution,
const mfem::ParGridFunction &projected_potential,
const mfem::Vector &projected_gradient,
const double mass,
const double stellar_radius
) {
@@ -364,7 +381,7 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
solution.phi.GetTrueDofs(solution_potential);
projected_potential.GetTrueDofs(projection_potential);
mfem::ParGridFunction projected_gradient_grid_function(fem.RT_fes.get());
mfem::ParGridFunction projected_gradient_grid_function(fem.gravityFluxFes.get());
projected_gradient_grid_function.SetFromTrueDofs(projected_gradient);
double local_solution_gradient_error = 0.0;
@@ -374,29 +391,34 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
double local_projection_potential_error = 0.0;
double local_potential_norm = 0.0;
const int vacuum_attribute = fem.domain_mapper_stateless->GetVacuumElementAttribute();
const int vacuum_attribute = field_dof_test_utils::vacuum_material_attribute;
const int quadrature_order = diagnostic_quadrature_order(fem);
mean_field::mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Vector physical_position(3);
mfem::Vector analytic_gradient(3);
mfem::Vector solution_reference_gradient(3);
mfem::Vector projection_reference_gradient(3);
mfem::Vector solution_physical_gradient(3);
mfem::Vector projection_physical_gradient(3);
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);
const mfem::IntegrationRule& rule = mfem::IntRules.Get(
transformation->GetGeometryType(),
quadrature_order
);
mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element_id);
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);
const mfem::IntegrationPoint &point = rule.IntPoint(quadrature_point_id);
transformation->SetIntPoint(&point);
fem.mapping->GetPhysicalPoint(*transformation, point, physical_position);
fem.mapping->ComputeJacobian(*transformation, mapping_jacobian);
const double mapping_determinant = mapping_jacobian.Det();
mean_field::mapping::MappingPointContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*transformation, point, mapping_context) ==
mean_field::mapping::MappingStatus::valid,
"Invalid mapping in accuracy diagnostic."
);
physical_position = mapping_context.physical_position;
const mfem::DenseMatrix &mapping_jacobian = mapping_context.mapping_jacobian;
const double mapping_determinant = mapping_context.mapping_determinant;
MFEM_VERIFY(mapping_determinant > 0.0, "Non-positive mapping determinant in accuracy diagnostic.");
const double radius = physical_position.Norml2();
@@ -408,8 +430,7 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
analytic_gradient *= mean_field::utils::G * mass / (radius * radius * radius);
analytic_potential = -mean_field::utils::G * mass / radius;
} else {
analytic_gradient *= mean_field::utils::G * mass /
(stellar_radius * stellar_radius * stellar_radius);
analytic_gradient *= mean_field::utils::G * mass / (stellar_radius * stellar_radius * stellar_radius);
analytic_potential = -mean_field::utils::G * mass *
(3.0 * stellar_radius * stellar_radius - radius * radius) /
(2.0 * stellar_radius * stellar_radius * stellar_radius);
@@ -432,10 +453,10 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
local_solution_gradient_error += weight * (solution_physical_gradient * solution_physical_gradient);
local_projection_gradient_error += weight * (projection_physical_gradient * projection_physical_gradient);
local_gradient_norm += weight * (analytic_gradient * analytic_gradient);
local_solution_potential_error += weight *
(solution_potential_value - analytic_potential) * (solution_potential_value - analytic_potential);
local_projection_potential_error += weight *
(projection_potential_value - analytic_potential) * (projection_potential_value - analytic_potential);
local_solution_potential_error += weight * (solution_potential_value - analytic_potential) *
(solution_potential_value - analytic_potential);
local_projection_potential_error += weight * (projection_potential_value - analytic_potential) *
(projection_potential_value - analytic_potential);
local_potential_norm += weight * analytic_potential * analytic_potential;
}
}
@@ -446,8 +467,8 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
};
std::array<double, 6> global_values{};
MPI_Allreduce(
local_values.data(), global_values.data(), static_cast<int>(local_values.size()),
MPI_DOUBLE, MPI_SUM, fem.L2_fes->GetComm()
local_values.data(), global_values.data(), static_cast<int>(local_values.size()), MPI_DOUBLE, MPI_SUM,
fem.mesh->GetComm()
);
const AccuracyBudgetEnergies energies = measure_stellar_energies(fem, density, solution);
@@ -460,17 +481,16 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
metrics.direct_relative_residual = reduced_gravity_relative_residual(fem, density, displacement, solution);
metrics.gradient_relative_error = std::sqrt(global_values[0] / global_values[2]);
metrics.gradient_projection_relative_error = std::sqrt(global_values[1] / global_values[2]);
metrics.gradient_solution_projection_gap = mapped_hdiv_relative_gap(
fem, displacement, solution_gradient, projected_gradient
);
metrics.gradient_solution_projection_gap =
mapped_hdiv_relative_gap(fem, displacement, solution_gradient, projected_gradient);
metrics.potential_relative_error = std::sqrt(global_values[3] / global_values[5]);
metrics.potential_projection_relative_error = std::sqrt(global_values[4] / global_values[5]);
mfem::Vector potential_difference(solution_potential);
potential_difference -= projection_potential;
const double projection_potential_norm = global_norm(projection_potential, fem.L2_fes->GetComm());
const double projection_potential_norm = global_norm(projection_potential, fem.gravityPotentialFes->GetComm());
REQUIRE(projection_potential_norm > 0.0);
metrics.potential_solution_projection_gap = global_norm(potential_difference, fem.L2_fes->GetComm()) /
projection_potential_norm;
metrics.potential_solution_projection_gap =
global_norm(potential_difference, fem.gravityPotentialFes->GetComm()) / projection_potential_norm;
metrics.binding_relative_error = std::abs(energies.binding - analytic_energy) / std::abs(analytic_energy);
metrics.virial_relative_error = std::abs(energies.virial - analytic_energy) / std::abs(analytic_energy);
metrics.virial_consistency_error = std::abs(energies.binding - energies.virial) /
@@ -479,8 +499,8 @@ static AccuracyBudgetMetrics measure_monopole_accuracy(
}
static void run_monopole_case(
const std::string& sweep_name,
const std::string& case_name,
const std::string &sweep_name,
const std::string &case_name,
mean_field::utils::Args args,
const double solver_tolerance,
const int quadrature_boost
@@ -491,19 +511,16 @@ static void run_monopole_case(
args.quadrature.global_boost = quadrature_boost;
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(fem.mapping != nullptr);
REQUIRE(fem.domain_mapper_stateless != nullptr);
REQUIRE(fem.domainMapperStateless != nullptr);
const double stellar_radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double density_value = mass / ((4.0 / 3.0) * M_PI * stellar_radius * stellar_radius * stellar_radius);
mfem::ParGridFunction displacement(fem.Vec_H1_fes.get());
mfem::ParGridFunction displacement(fem.displacementFes.get());
displacement = 0.0;
fem.mapping->ResetDisplacement();
mean_field::physics::update_stiffness_matrix(fem);
mfem::GridFunction density(fem.L2_fes.get());
*fem.displacement = 0.0;
mfem::GridFunction density(fem.densityFes.get());
density = density_value;
zero_vacuum_density(fem, density);
mean_field::analysis::conserve_mass(fem, density, mass);
@@ -511,40 +528,26 @@ static void run_monopole_case(
fem.Q = mean_field::physics::compute_quadrupole_moment_tensor(fem, density, fem.com);
const mean_field::physics::GravitySolution solution =
mean_field::physics::grav_potential_new(fem, args, density, displacement);
mean_field::physics::solve_gravity_field(fem, args, density, displacement);
auto analytic_potential = [mass, stellar_radius](const mfem::Vector& position) {
auto analytic_potential = [mass, stellar_radius](const mfem::Vector &position) {
const double radius = position.Norml2();
if (radius >= stellar_radius) {
return -mean_field::utils::G * mass / radius;
}
return -mean_field::utils::G * mass *
(3.0 * stellar_radius * stellar_radius - radius * radius) /
return -mean_field::utils::G * mass * (3.0 * stellar_radius * stellar_radius - radius * radius) /
(2.0 * stellar_radius * stellar_radius * stellar_radius);
};
mean_field::mapping::PhysicalPositionFunctionCoefficient potential_coefficient(
*fem.mapping,
analytic_potential
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, analytic_potential
);
mfem::ParGridFunction projected_potential(fem.L2_fes.get());
mfem::ParGridFunction projected_potential(fem.gravityPotentialFes.get());
projected_potential.ProjectCoefficient(potential_coefficient);
const mfem::Vector projected_gradient = project_monopole_gradient(
fem,
displacement,
mass,
stellar_radius
);
const mfem::Vector projected_gradient = project_monopole_gradient(fem, displacement, mass, stellar_radius);
const AccuracyBudgetMetrics metrics = measure_monopole_accuracy(
fem,
density,
displacement,
solution,
projected_potential,
projected_gradient,
mass,
stellar_radius
fem, density, displacement, solution, projected_potential, projected_gradient, mass, stellar_radius
);
REQUIRE(std::isfinite(metrics.direct_relative_residual));
@@ -553,15 +556,11 @@ static void run_monopole_case(
REQUIRE(std::isfinite(metrics.virial_consistency_error));
record_experiment_result(
sweep_name,
case_name,
{
{"solver_rtol", std::to_string(solver_tolerance)},
sweep_name, case_name,
{{"solver_rtol", std::to_string(solver_tolerance)},
{"quadrature_global_boost", std::to_string(quadrature_boost)},
{"mesh_file", args.mesh_file}
},
{
{"direct_relative_residual", metrics.direct_relative_residual},
{"mesh_file", args.mesh_file}},
{{"direct_relative_residual", metrics.direct_relative_residual},
{"gradient_relative_error", metrics.gradient_relative_error},
{"gradient_projection_relative_error", metrics.gradient_projection_relative_error},
{"gradient_solution_projection_gap", metrics.gradient_solution_projection_gap},
@@ -570,47 +569,37 @@ static void run_monopole_case(
{"potential_solution_projection_gap", metrics.potential_solution_projection_gap},
{"binding_relative_error", metrics.binding_relative_error},
{"virial_relative_error", metrics.virial_relative_error},
{"virial_consistency_error", metrics.virial_consistency_error}
}
{"virial_consistency_error", metrics.virial_consistency_error}}
);
}
TEST_CASE("Uniform Monopole Accuracy Budget: Solver Tolerance", tags::gravity & tags::accuracy & tags::integration) {
TEST_CASE(
"Uniform Monopole Accuracy Budget: Solver Tolerance",
tags::gravity_analytic_accuracy
) {
const mean_field::utils::Args args = test_utils::setup_args();
constexpr std::array<double, 4> solver_tolerances{1.0e-8, 1.0e-10, 1.0e-12, 1.0e-14};
for (const double solver_tolerance : solver_tolerances) {
run_monopole_case(
"solver_tolerance",
"uniform_monopole",
args,
solver_tolerance,
0
);
run_monopole_case("solver_tolerance", "uniform_monopole", args, solver_tolerance, 0);
}
}
TEST_CASE("Uniform Monopole Accuracy Budget: Quadrature", tags::gravity & tags::accuracy & tags::integration) {
TEST_CASE(
"Uniform Monopole Accuracy Budget: Quadrature",
tags::gravity_analytic_accuracy
) {
const mean_field::utils::Args args = test_utils::setup_args();
constexpr std::array<int, 3> quadrature_boosts{0, 4, 8};
for (const int quadrature_boost : quadrature_boosts) {
run_monopole_case(
"quadrature",
"uniform_monopole",
args,
1.0e-13,
quadrature_boost
);
run_monopole_case("quadrature", "uniform_monopole", args, 1.0e-13, quadrature_boost);
}
}
TEST_CASE("Uniform Monopole Accuracy Budget: Projection Decomposition", tags::gravity & tags::accuracy & tags::integration) {
run_monopole_case(
"projection_decomposition",
"uniform_monopole",
test_utils::setup_args(),
1.0e-13,
0
);
TEST_CASE(
"Uniform Monopole Accuracy Budget: Projection Decomposition",
tags::gravity_analytic_accuracy
) {
run_monopole_case("projection_decomposition", "uniform_monopole", test_utils::setup_args(), 1.0e-13, 0);
}

View File

@@ -0,0 +1,241 @@
#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
class GravityUnknownJacobian final : public mfem::Operator {
public:
explicit GravityUnknownJacobian(
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator
)
: mfem::Operator(
stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue) +
stellarOperator.GetLayout().size(experiment::null_space::gravityPotentialValue)
),
m_stellarOperator(stellarOperator),
m_gravityGradientSize(stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue)) {
MFEM_VERIFY(Width() == Height(), "The reduced gravity Jacobian must be square.");
}
void Mult(
const mfem::Vector &gravityDirection,
mfem::Vector &gravityAction
) const override {
MFEM_VERIFY(gravityDirection.Size() == Width(), "The reduced gravity direction has the wrong size.");
const mfem::Vector gravityGradientDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()), m_gravityGradientSize
);
const mfem::Vector gravityPotentialDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()) + m_gravityGradientSize,
Width() - m_gravityGradientSize
);
m_stellarOperator.GetGravityOperator().ApplyGravityUnknowns(
gravityGradientDirection, gravityPotentialDirection,
m_stellarOperator.GetGravityContext().GetGeometryContext(), gravityAction
);
}
[[nodiscard]] int gravity_gradient_size() const noexcept {
return m_gravityGradientSize;
}
private:
const mean_field::operators::PreparedStellarEquilibriumOperator &m_stellarOperator;
int m_gravityGradientSize;
};
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + experiment::null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
[[nodiscard]] mfem::Vector gravity_residual_blocks(
const mfem::Vector &completeAction,
const mean_field::operators::StellarEquilibriumLayout &layout
) {
const mfem::Vector gradient = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityGradientResidual
);
const mfem::Vector potential = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityPotentialResidual
);
mfem::Vector result(gradient.Size() + potential.Size());
mfem::Vector(result.GetData(), gradient.Size()) = gradient;
mfem::Vector(result.GetData() + gradient.Size(), potential.Size()) = potential;
return result;
}
void assign_gravity_completion(
mfem::Vector &completeDirection,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mfem::Vector &gravityCompletion,
const int gravityGradientSize
) {
const mfem::Vector gravityGradient(
const_cast<mfem::real_t *>(gravityCompletion.GetData()), gravityGradientSize
);
const mfem::Vector gravityPotential(
const_cast<mfem::real_t *>(gravityCompletion.GetData()) + gravityGradientSize,
gravityCompletion.Size() - gravityGradientSize
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityGradientValue, gravityGradient
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityPotentialValue, gravityPotential
);
}
} // namespace
TEST_CASE(
"Gravity-Completed Reduced Surface Mode Responses Of The Stellar Equilibrium Jacobian",
"[null_space][surface_modes][gravity_completed]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = 1.0e-11;
args.p.atol = std::min(args.p.atol, 1.0e-13);
args.p.max_iters = std::max(args.p.max_iters, 2000);
experiment::null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const auto modes = experiment::null_space::make_surface_modes(fixture);
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
const int totalCases = static_cast<int>(rotationFractions.size() * modes.size());
int completedCases = 0;
for (const double rotationFraction : rotationFractions) {
const mean_field::physics::RigidRotation rotation = fixture.rotation(rotationFraction);
fixture.prepare(fixture.state(), rotation);
GravityUnknownJacobian gravityUnknownJacobian(fixture.stellar_operator());
mean_field::operators::ReducedGravityFieldPreconditioner gravityPreconditioner(
fixture.fem(), fixture.stellar_operator().GetGravityContext().GetGeometryContext()
);
mfem::MINRESSolver gravitySolver(communicator);
gravitySolver.SetOperator(gravityUnknownJacobian);
gravitySolver.SetPreconditioner(gravityPreconditioner);
gravitySolver.SetRelTol(1.0e-11);
gravitySolver.SetAbsTol(1.0e-13);
gravitySolver.SetMaxIter(2000);
gravitySolver.SetPrintLevel(1);
for (const experiment::null_space::SurfaceMode &mode : modes) {
experiment::null_space::report_progress(
communicator, "solving the gravity completion for " + mode.name + " at rotation fraction " +
std::to_string(rotationFraction) + " (" + std::to_string(completedCases + 1) + "/" +
std::to_string(totalCases) + ")"
);
const mfem::Vector surfaceOnlyAction = fixture.jacobian_action(mode.direction);
mfem::Vector gravityRightHandSide =
gravity_residual_blocks(surfaceOnlyAction, fixture.stellar_operator().GetLayout());
gravityRightHandSide *= -1.0;
mfem::Vector gravityCompletion(gravityUnknownJacobian.Width());
gravityCompletion = 0.0;
gravitySolver.Mult(gravityRightHandSide, gravityCompletion);
REQUIRE(gravitySolver.GetConverged());
mfem::Vector gravitySolveAction;
gravityUnknownJacobian.Mult(gravityCompletion, gravitySolveAction);
mfem::Vector gravitySolveResidual(gravitySolveAction);
gravitySolveResidual -= gravityRightHandSide;
const double gravityRightHandSideNorm =
experiment::null_space::global_norm(gravityRightHandSide, communicator);
const double gravitySolveResidualNorm =
experiment::null_space::global_norm(gravitySolveResidual, communicator);
const double gravitySolveRelativeResidual =
gravitySolveResidualNorm / std::max(gravityRightHandSideNorm, std::numeric_limits<double>::epsilon());
REQUIRE(std::isfinite(gravitySolveRelativeResidual));
mfem::Vector completedDirection(mode.direction);
assign_gravity_completion(
completedDirection, fixture.stellar_operator().GetLayout(), gravityCompletion,
gravityUnknownJacobian.gravity_gradient_size()
);
const mfem::Vector completedAction = fixture.jacobian_action(completedDirection);
std::map<std::string, double> metrics{
{"surface_only_input_norm", experiment::null_space::global_norm(mode.direction, communicator)},
{"gravity_completion_norm", experiment::null_space::global_norm(gravityCompletion, communicator)},
{"completed_input_norm", experiment::null_space::global_norm(completedDirection, communicator)},
{"surface_only_action_norm", experiment::null_space::global_norm(surfaceOnlyAction, communicator)},
{"gravity_completed_action_norm", experiment::null_space::global_norm(completedAction, communicator)},
{"gravity_solve_rhs_norm", gravityRightHandSideNorm},
{"gravity_solve_residual_norm", gravitySolveResidualNorm},
{"gravity_solve_relative_residual", gravitySolveRelativeResidual},
{"gravity_solve_iterations", static_cast<double>(gravitySolver.GetNumIterations())},
{"gravity_solve_final_norm", gravitySolver.GetFinalNorm()}
};
add_block_metrics(
metrics, "surface_only_",
experiment::null_space::residual_block_norms(
surfaceOnlyAction, fixture.stellar_operator().GetLayout(), communicator
)
);
add_block_metrics(
metrics, "gravity_completed_",
experiment::null_space::residual_block_norms(
completedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
if (rank == 0) {
experiment::record_experiment_result(
"gravity_completed_reduced_surface_modes", mode.name,
{{"mode_kind", experiment::null_space::surface_mode_kind_name(mode.kind)},
{"axis", std::to_string(mode.axis)},
{"rotation_fraction_of_keplerian", std::to_string(rotationFraction)},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
std::move(metrics)
);
}
++completedCases;
experiment::null_space::report_progress(
communicator, "completed " + std::to_string(completedCases) + "/" + std::to_string(totalCases) +
" gravity-completed reduced surface-mode cases"
);
}
}
experiment::null_space::report_progress(
communicator, "gravity-completed reduced surface-mode probe complete; writing CSV output"
);
}

View File

@@ -0,0 +1,591 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import mean_field;
import test_helpers;
namespace {
using Clock = std::chrono::steady_clock;
namespace backend = mean_field::preconditioning::backend;
namespace preconditioning = mean_field::preconditioning;
[[nodiscard]] const char *buildConfiguration() noexcept {
#ifdef NDEBUG
return "release";
#else
return "debug";
#endif
}
[[nodiscard]] double maximumRankSeconds(
const Clock::time_point start,
const MPI_Comm communicator
) {
const double localSeconds = std::chrono::duration<double>(Clock::now() - start).count();
double maximumSeconds = 0.0;
MPI_Allreduce(&localSeconds, &maximumSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return maximumSeconds;
}
[[nodiscard]] double globalNorm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
const double localSquared = vector * vector;
double globalSquared = 0.0;
MPI_Allreduce(&localSquared, &globalSquared, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(std::max(globalSquared, 0.0));
}
[[nodiscard]] double globalDot(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
const double localDot = left * right;
double result = 0.0;
MPI_Allreduce(&localDot, &result, 1, MPI_DOUBLE, MPI_SUM, communicator);
return result;
}
void announce(
const MPI_Comm communicator,
const std::string &message
) {
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << message << std::endl;
}
}
class ReducedGravityOperator final : public mfem::Operator {
public:
explicit ReducedGravityOperator(
const mean_field::operators::context::gravity_field::GravityFieldGeometryContext &context
)
: mfem::Operator(
context.GetMassOperator().GetFluxMap().reduced_size() +
context.GetSourceOperator().GetPotentialMap().reduced_size()
),
m_mass(&context.GetMassOperator()),
m_divergence(
context.GetDivergenceOperator(),
context.GetMassOperator().GetFluxMap(),
context.GetSourceOperator().GetPotentialMap()
),
m_offsets(3),
m_gradientWorkspace(context.GetMassOperator().GetFluxMap().reduced_size()) {
m_offsets[0] = 0;
m_offsets[1] = context.GetMassOperator().GetFluxMap().reduced_size();
m_offsets[2] = Height();
}
void Mult(
const mfem::Vector &state,
mfem::Vector &residual
) const override {
if (state.Size() != Width() || residual.Size() != Height()) {
throw std::invalid_argument("The reduced gravity experiment requires preallocated compatible vectors.");
}
const mfem::Vector gradient(
const_cast<mfem::real_t *>(state.GetData()) + m_offsets[0], m_offsets[1] - m_offsets[0]
);
const mfem::Vector potential(
const_cast<mfem::real_t *>(state.GetData()) + m_offsets[1], m_offsets[2] - m_offsets[1]
);
mfem::Vector gradientResidual(residual.GetData() + m_offsets[0], m_offsets[1] - m_offsets[0]);
mfem::Vector potentialResidual(residual.GetData() + m_offsets[1], m_offsets[2] - m_offsets[1]);
m_mass->Mult(gradient, gradientResidual);
m_divergence.MultTranspose(potential, m_gradientWorkspace);
gradientResidual += m_gradientWorkspace;
m_divergence.Mult(gradient, potentialResidual);
}
private:
const mfem::Operator *m_mass;
preconditioning::ReducedGravityDivergenceOperator m_divergence;
mfem::Array<int> m_offsets;
mutable mfem::Vector m_gradientWorkspace;
};
[[nodiscard]] std::map<
std::string,
std::string>
commonParameters(
const std::string &candidate,
const std::string &measurement,
const int dimension
) {
return {
{"build_configuration", buildConfiguration()},
{"candidate", candidate},
{"experiment_schema", "p4_reduced_gravity_v1"},
{"factorization", candidate},
{"measurement", measurement},
{"mesh_file", test_utils::setup_args().mesh_file},
{"operator", "reduced_gravity_saddle_point"},
{"preconditioned_product", "G M^-1"},
{"root_dimension", std::to_string(dimension)}
};
}
void recordSpectrum(
const std::string &candidate,
const mean_field::solver::ArnoldiSpectralMeasurement &spectrum,
const int dimension,
const double setupSeconds
) {
experiment::record_experiment_result(
"gravity_preconditioning_p4", candidate + "_spectrum",
commonParameters(candidate, "arnoldi_summary", dimension),
{{"setup_seconds_maximum_rank", setupSeconds},
{"requested_dimension", static_cast<double>(spectrum.requestedDimension)},
{"achieved_dimension", static_cast<double>(spectrum.achievedDimension)},
{"operator_applications", static_cast<double>(spectrum.operatorApplications)},
{"measurement_seconds_maximum_rank", spectrum.measurementSecondsMaximumRank},
{"operator_application_seconds_maximum_rank", spectrum.operatorApplicationSecondsMaximumRank},
{"projected_condition_proxy", spectrum.projectedConditionProxy},
{"projected_largest_singular_value", spectrum.projectedLargestSingularValue},
{"projected_smallest_singular_value", spectrum.projectedSmallestSingularValue},
{"centroid_real_part", spectrum.centroidRealPart},
{"rms_distance_from_one", spectrum.rmsDistanceFromOne},
{"rms_cluster_radius", spectrum.rmsClusterRadius},
{"minimum_magnitude", spectrum.minimumMagnitude},
{"maximum_magnitude", spectrum.maximumMagnitude},
{"minimum_real_part", spectrum.minimumRealPart},
{"maximum_real_part", spectrum.maximumRealPart},
{"maximum_absolute_imaginary_part", spectrum.maximumAbsoluteImaginaryPart},
{"negative_real_part_count", static_cast<double>(spectrum.negativeRealPartCount)},
{"converged_ritz_value_count", static_cast<double>(spectrum.convergedRitzValueCount)},
{"conjugate_pair_defect", spectrum.conjugatePairDefect},
{"projected_departure_from_normality", spectrum.projectedDepartureFromNormality},
{"field_of_values_minimum_real_part", spectrum.projectedFieldOfValuesMinimumRealPart},
{"field_of_values_maximum_real_part", spectrum.projectedFieldOfValuesMaximumRealPart}}
);
for (std::size_t index = 0; index < spectrum.ritzValues.size(); ++index) {
const auto &value = spectrum.ritzValues[index];
experiment::record_experiment_result(
"gravity_preconditioning_p4", candidate + "_ritz_" + std::to_string(index),
commonParameters(candidate, "ritz_value", dimension),
{{"ritz_index", static_cast<double>(index)},
{"real_part", value.realPart},
{"imaginary_part", value.imaginaryPart},
{"magnitude", value.magnitude},
{"distance_from_one", value.distanceFromOne},
{"residual_estimate", value.residualEstimate},
{"relative_residual_estimate", value.relativeResidualEstimate},
{"converged", value.converged ? 1.0 : 0.0}}
);
}
}
void measureCandidate(
const std::string &candidate,
mfem::Solver &inversePreconditioner,
const double setupSeconds,
const ReducedGravityOperator &gravityOperator,
const mfem::Vector &rightHandSide,
const mfem::Vector &arnoldiDirection,
const MPI_Comm communicator
) {
constexpr int arnoldiDimension = 32;
mean_field::solver::InstrumentedOperator instrumentedGravity(gravityOperator);
mean_field::solver::InstrumentedPreconditioner instrumentedPreconditioner(inversePreconditioner);
mean_field::solver::ResidualHistoryMonitor monitor;
mfem::FGMRESSolver krylov(communicator);
krylov.SetPreconditioner(instrumentedPreconditioner);
krylov.SetOperator(instrumentedGravity);
krylov.SetMonitor(monitor);
krylov.SetRelTol(1.0e-8);
krylov.SetAbsTol(1.0e-12);
krylov.SetMaxIter(100);
krylov.SetKDim(30);
krylov.SetPrintLevel(0);
mfem::Vector solution(gravityOperator.Width());
solution = 0.0;
announce(communicator, "P4 reduced gravity: solving with " + candidate);
const Clock::time_point solveStart = Clock::now();
krylov.Mult(rightHandSide, solution);
const double solveSeconds = maximumRankSeconds(solveStart, communicator);
mfem::Vector reconstructed(rightHandSide.Size());
gravityOperator.Mult(solution, reconstructed);
reconstructed -= rightHandSide;
const double relativeResidual =
globalNorm(reconstructed, communicator) /
std::max(globalNorm(rightHandSide, communicator), std::numeric_limits<double>::epsilon());
const auto jacobianStatistics = instrumentedGravity.GetStatistics();
const auto preconditionerStatistics = instrumentedPreconditioner.GetStatistics();
REQUIRE(std::isfinite(relativeResidual));
experiment::record_experiment_result(
"gravity_preconditioning_p4", candidate + "_linear_solve",
commonParameters(candidate, "linear_solve", gravityOperator.Width()),
{{"setup_seconds_maximum_rank", setupSeconds},
{"solver_converged", krylov.GetConverged() ? 1.0 : 0.0},
{"outer_iterations", static_cast<double>(krylov.GetNumIterations())},
{"true_relative_residual", relativeResidual},
{"solve_seconds_maximum_rank", solveSeconds},
{"gravity_applications", static_cast<double>(jacobianStatistics.applications)},
{"gravity_application_seconds", jacobianStatistics.totalSeconds},
{"preconditioner_applications", static_cast<double>(preconditionerStatistics.applications)},
{"preconditioner_application_seconds", preconditionerStatistics.totalSeconds},
{"preconditioner_maximum_application_seconds", preconditionerStatistics.maximumSeconds}}
);
instrumentedGravity.ResetStatistics();
instrumentedPreconditioner.ResetStatistics();
mean_field::solver::FixedRightPreconditionedOperator product(instrumentedGravity, instrumentedPreconditioner);
announce(communicator, "P4 reduced gravity: measuring " + candidate + " Arnoldi spectrum");
const auto spectrum = mean_field::solver::measureArnoldiSpectrum(
product, arnoldiDirection, communicator,
{.krylovDimension = arnoldiDimension,
.breakdownRelativeTolerance = 1.0e-13,
.ritzConvergenceRelativeTolerance = 1.0e-7,
.reorthogonalize = true}
);
recordSpectrum(candidate, spectrum, gravityOperator.Width(), setupSeconds);
}
template <
preconditioning::GravityFactorizationPolicy Policy,
backend::Registered MassBackend = backend::Diagonal>
requires backend::Compatible<
MassBackend,
preconditioning::GravityMassInverseCharacteristics>
void prepareAndMeasureTypedCandidate(
const std::string &candidate,
Policy policy,
const mean_field::fem::FEM &finiteElements,
const mean_field::operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
const ReducedGravityOperator &gravityOperator,
const mfem::Vector &rightHandSide,
const mfem::Vector &arnoldiDirection,
const MPI_Comm communicator,
const int amgCycles = 1,
MassBackend massBackend = {}
) {
const Clock::time_point setupStart = Clock::now();
const auto block = preconditioning::GravityFieldBlock(
std::move(massBackend), backend::HypreBoomerAMG{backend::FixedCycles{.cycles = amgCycles}}, policy
);
auto prepared = preconditioning::prepare(finiteElements, geometryContext, block);
const double setupTime = maximumRankSeconds(setupStart, communicator);
const auto &massOperator = geometryContext.GetMassOperator();
const mfem::Vector firstMassRightHandSide =
gravity_prepared_test_utils::make_deterministic_vector(massOperator.Width(), 0.41);
const mfem::Vector secondMassRightHandSide =
gravity_prepared_test_utils::make_deterministic_vector(massOperator.Width(), 1.17);
mfem::Vector firstMassAction(massOperator.Width());
mfem::Vector secondMassAction(massOperator.Width());
prepared.GetMassInverse().Mult(firstMassRightHandSide, firstMassAction);
prepared.GetMassInverse().Mult(secondMassRightHandSide, secondMassAction);
mfem::Vector recoveredMassRightHandSide(massOperator.Height());
massOperator.Mult(firstMassAction, recoveredMassRightHandSide);
recoveredMassRightHandSide -= firstMassRightHandSide;
const double massRecoveryDefect =
globalNorm(recoveredMassRightHandSide, communicator) / globalNorm(firstMassRightHandSide, communicator);
const double firstSecond = globalDot(firstMassRightHandSide, secondMassAction, communicator);
const double secondFirst = globalDot(secondMassRightHandSide, firstMassAction, communicator);
const double massSymmetryDefect =
std::abs(firstSecond - secondFirst) / std::max({1.0, std::abs(firstSecond), std::abs(secondFirst)});
const double massPositiveRayleigh = globalDot(firstMassRightHandSide, firstMassAction, communicator) /
std::max(
globalDot(firstMassRightHandSide, firstMassRightHandSide, communicator),
std::numeric_limits<double>::min()
);
const auto &schurOperator = prepared.GetPotentialSchurSurrogate();
const mfem::Vector schurRightHandSide =
gravity_prepared_test_utils::make_deterministic_vector(schurOperator.Width(), 0.73);
mfem::Vector schurAction(schurOperator.Width());
prepared.GetPotentialSchurInverse().Mult(schurRightHandSide, schurAction);
mfem::Vector recoveredSchurRightHandSide(schurOperator.Height());
schurOperator.Mult(schurAction, recoveredSchurRightHandSide);
recoveredSchurRightHandSide -= schurRightHandSide;
const double schurRecoveryDefect =
globalNorm(recoveredSchurRightHandSide, communicator) / globalNorm(schurRightHandSide, communicator);
experiment::record_experiment_result(
"gravity_preconditioning_p4", candidate + "_block_quality",
commonParameters(candidate, "block_inverse_quality", gravityOperator.Width()),
{{"amg_cycles", static_cast<double>(amgCycles)},
{"mass_inverse_recovery_defect", massRecoveryDefect},
{"mass_inverse_symmetry_defect", massSymmetryDefect},
{"mass_inverse_positive_rayleigh", massPositiveRayleigh},
{"potential_schur_inverse_recovery_defect", schurRecoveryDefect}}
);
measureCandidate(
candidate, prepared, setupTime, gravityOperator, rightHandSide, arnoldiDirection, communicator
);
}
[[nodiscard]] int firstReportedThresholdIteration(
const std::vector<mean_field::solver::IterationResidualMeasurement> &history,
const double initialNorm,
const double relativeThreshold
) {
if (!std::isfinite(initialNorm) || initialNorm <= 0.0) {
return -1;
}
for (const auto &sample : history) {
if (std::abs(sample.reportedNorm) / initialNorm <= relativeThreshold) {
return sample.iteration;
}
}
return -1;
}
} // namespace
TEST_CASE(
"Reduced Gravity P4 Factorization Comparison",
"[preconditioning][gravity][diagnostics][experiment][spectrum]"
) {
const auto arguments = test_utils::setup_args();
mean_field::fem::FEM finiteElements = mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
const MPI_Comm communicator = finiteElements.mesh->GetComm();
using GeometryContext = mean_field::operators::context::gravity_field::GravityFieldGeometryContext;
GeometryContext geometryContext(finiteElements, *finiteElements.domainMapperStateless);
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
const mfem::Vector displacement = geometryContext.GetDisplacementMap().gather(displacementTrue);
geometryContext.PreparePrimal(displacement, {.value = 1}, {.value = 1});
ReducedGravityOperator gravityOperator(geometryContext);
const mfem::Vector exact = gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.37);
mfem::Vector rightHandSide(gravityOperator.Height());
gravityOperator.Mult(exact, rightHandSide);
const mfem::Vector arnoldiDirection =
gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.83);
const Clock::time_point legacySetupStart = Clock::now();
mean_field::operators::ReducedGravityFieldPreconditioner legacy(finiteElements, geometryContext);
const double legacySetupTime = maximumRankSeconds(legacySetupStart, communicator);
measureCandidate(
"legacy_block_diagonal", legacy, legacySetupTime, gravityOperator, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureTypedCandidate(
"typed_block_diagonal", preconditioning::GravityBlockDiagonal{}, finiteElements, geometryContext,
gravityOperator, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureTypedCandidate(
"lower_triangular", preconditioning::GravityLowerTriangular{}, finiteElements, geometryContext, gravityOperator,
rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureTypedCandidate(
"upper_triangular", preconditioning::GravityUpperTriangular{}, finiteElements, geometryContext, gravityOperator,
rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureTypedCandidate(
"approximate_ldu", preconditioning::GravityApproximateLDU{}, finiteElements, geometryContext, gravityOperator,
rightHandSide, arnoldiDirection, communicator
);
}
TEST_CASE(
"Reduced Gravity P4 Fixed AMG Cycle Sweep",
"[preconditioning][gravity][diagnostics][experiment][amg_cycle_sweep]"
) {
const auto arguments = test_utils::setup_args();
mean_field::fem::FEM finiteElements = mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
const MPI_Comm communicator = finiteElements.mesh->GetComm();
using GeometryContext = mean_field::operators::context::gravity_field::GravityFieldGeometryContext;
GeometryContext geometryContext(finiteElements, *finiteElements.domainMapperStateless);
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
const mfem::Vector displacement = geometryContext.GetDisplacementMap().gather(displacementTrue);
geometryContext.PreparePrimal(displacement, {.value = 1}, {.value = 1});
ReducedGravityOperator gravityOperator(geometryContext);
const mfem::Vector exact = gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.37);
mfem::Vector rightHandSide(gravityOperator.Height());
gravityOperator.Mult(exact, rightHandSide);
const mfem::Vector arnoldiDirection =
gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.83);
for (const int cycles : {1, 2, 3, 4, 6, 8}) {
prepareAndMeasureTypedCandidate(
"approximate_ldu_amg_cycles_" + std::to_string(cycles), preconditioning::GravityApproximateLDU{},
finiteElements, geometryContext, gravityOperator, rightHandSide, arnoldiDirection, communicator, cycles
);
}
for (const int order : {2, 3, 4, 5}) {
for (const int cycles : {1, 2, 3}) {
prepareAndMeasureTypedCandidate(
"approximate_ldu_chebyshev_" + std::to_string(order) + "_amg_cycles_" + std::to_string(cycles),
preconditioning::GravityApproximateLDU{}, finiteElements, geometryContext, gravityOperator,
rightHandSide, arnoldiDirection, communicator, cycles,
backend::MatrixFreeChebyshev{.order = order, .powerIterations = 20}
);
}
}
}
TEST_CASE(
"Reduced Gravity P4 LDU Extended FGMRES Convergence",
"[preconditioning][gravity][diagnostics][experiment][p4_followup][extended_solve]"
) {
constexpr int maximumIterations = 200;
constexpr int restartDimension = 30;
const auto arguments = test_utils::setup_args();
mean_field::fem::FEM finiteElements = mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
const MPI_Comm communicator = finiteElements.mesh->GetComm();
using GeometryContext = mean_field::operators::context::gravity_field::GravityFieldGeometryContext;
GeometryContext geometryContext(finiteElements, *finiteElements.domainMapperStateless);
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
const mfem::Vector displacement = geometryContext.GetDisplacementMap().gather(displacementTrue);
geometryContext.PreparePrimal(displacement, {.value = 1}, {.value = 1});
ReducedGravityOperator gravityOperator(geometryContext);
const mfem::Vector exact = gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.37);
mfem::Vector rightHandSide(gravityOperator.Height());
gravityOperator.Mult(exact, rightHandSide);
const Clock::time_point setupStart = Clock::now();
const auto block = preconditioning::GravityFieldBlock(
backend::Diagonal{}, backend::HypreBoomerAMG{backend::FixedCycles{.cycles = 1}},
preconditioning::GravityApproximateLDU{}
);
auto prepared = preconditioning::prepare(finiteElements, geometryContext, block);
const double setupTime = maximumRankSeconds(setupStart, communicator);
mean_field::solver::InstrumentedOperator instrumentedGravity(gravityOperator);
mean_field::solver::InstrumentedPreconditioner instrumentedPreconditioner(prepared);
mean_field::solver::ResidualHistoryMonitor monitor;
mfem::FGMRESSolver krylov(communicator);
krylov.SetPreconditioner(instrumentedPreconditioner);
krylov.SetOperator(instrumentedGravity);
krylov.SetMonitor(monitor);
krylov.SetRelTol(1.0e-8);
krylov.SetAbsTol(1.0e-12);
krylov.SetMaxIter(maximumIterations);
krylov.SetKDim(restartDimension);
krylov.SetPrintLevel(0);
mfem::Vector solution(gravityOperator.Width());
solution = 0.0;
announce(communicator, "P4 follow-up: running 200-iteration approximate-LDU FGMRES");
const Clock::time_point solveStart = Clock::now();
krylov.Mult(rightHandSide, solution);
const double solveSeconds = maximumRankSeconds(solveStart, communicator);
mfem::Vector reconstructed(rightHandSide.Size());
gravityOperator.Mult(solution, reconstructed);
reconstructed -= rightHandSide;
const double trueRelativeResidual =
globalNorm(reconstructed, communicator) /
std::max(globalNorm(rightHandSide, communicator), std::numeric_limits<double>::epsilon());
const double initialNorm = std::abs(krylov.GetInitialNorm());
const auto &history = monitor.GetHistory();
const int iteration1e4 = firstReportedThresholdIteration(history, initialNorm, 1.0e-4);
const int iteration1e6 = firstReportedThresholdIteration(history, initialNorm, 1.0e-6);
const int iteration1e8 = firstReportedThresholdIteration(history, initialNorm, 1.0e-8);
REQUIRE(std::isfinite(trueRelativeResidual));
REQUIRE_FALSE(history.empty());
experiment::record_experiment_result(
"gravity_preconditioning_p4_followup", "approximate_ldu_extended_linear_solve",
commonParameters("approximate_ldu_extended", "linear_solve", gravityOperator.Width()),
{{"maximum_iterations", static_cast<double>(maximumIterations)},
{"restart_dimension", static_cast<double>(restartDimension)},
{"setup_seconds_maximum_rank", setupTime},
{"solver_converged", krylov.GetConverged() ? 1.0 : 0.0},
{"outer_iterations", static_cast<double>(krylov.GetNumIterations())},
{"reported_initial_residual_norm", initialNorm},
{"reported_final_residual_norm", std::abs(krylov.GetFinalNorm())},
{"reported_residual_reduction", initialNorm > 0.0 ? std::abs(krylov.GetFinalNorm()) / initialNorm : 0.0},
{"reported_iteration_to_1e-4", static_cast<double>(iteration1e4)},
{"reported_iteration_to_1e-6", static_cast<double>(iteration1e6)},
{"reported_iteration_to_1e-8", static_cast<double>(iteration1e8)},
{"true_relative_residual", trueRelativeResidual},
{"solve_seconds_maximum_rank", solveSeconds},
{"gravity_applications", static_cast<double>(instrumentedGravity.GetStatistics().applications)},
{"gravity_application_seconds", instrumentedGravity.GetStatistics().totalSeconds},
{"preconditioner_applications", static_cast<double>(instrumentedPreconditioner.GetStatistics().applications)},
{"preconditioner_application_seconds", instrumentedPreconditioner.GetStatistics().totalSeconds}}
);
for (std::size_t index = 0; index < history.size(); ++index) {
const auto &sample = history[index];
experiment::record_experiment_result(
"gravity_preconditioning_p4_followup", "approximate_ldu_history_" + std::to_string(index),
commonParameters("approximate_ldu_extended", "fgmres_residual_history", gravityOperator.Width()),
{{"history_sample", static_cast<double>(index)},
{"iteration", static_cast<double>(sample.iteration)},
{"reported_residual_norm", sample.reportedNorm},
{"reported_relative_residual", initialNorm > 0.0 ? std::abs(sample.reportedNorm) / initialNorm : 0.0},
{"final_measurement", sample.final ? 1.0 : 0.0}}
);
}
}
TEST_CASE(
"Reduced Gravity P4 LDU Extended Arnoldi Convergence",
"[preconditioning][gravity][diagnostics][experiment][spectrum][p4_followup][extended_arnoldi]"
) {
constexpr int arnoldiDimension = 96;
const auto arguments = test_utils::setup_args();
mean_field::fem::FEM finiteElements = mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
const MPI_Comm communicator = finiteElements.mesh->GetComm();
using GeometryContext = mean_field::operators::context::gravity_field::GravityFieldGeometryContext;
GeometryContext geometryContext(finiteElements, *finiteElements.domainMapperStateless);
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
displacementTrue = 0.0;
const mfem::Vector displacement = geometryContext.GetDisplacementMap().gather(displacementTrue);
geometryContext.PreparePrimal(displacement, {.value = 1}, {.value = 1});
ReducedGravityOperator gravityOperator(geometryContext);
const mfem::Vector arnoldiDirection =
gravity_prepared_test_utils::make_deterministic_vector(gravityOperator.Width(), 0.83);
const Clock::time_point setupStart = Clock::now();
const auto block = preconditioning::GravityFieldBlock(
backend::Diagonal{}, backend::HypreBoomerAMG{backend::FixedCycles{.cycles = 1}},
preconditioning::GravityApproximateLDU{}
);
auto prepared = preconditioning::prepare(finiteElements, geometryContext, block);
const double setupTime = maximumRankSeconds(setupStart, communicator);
mean_field::solver::InstrumentedOperator instrumentedGravity(gravityOperator);
mean_field::solver::InstrumentedPreconditioner instrumentedPreconditioner(prepared);
mean_field::solver::FixedRightPreconditionedOperator product(instrumentedGravity, instrumentedPreconditioner);
announce(communicator, "P4 follow-up: measuring the 96-vector approximate-LDU Arnoldi spectrum");
const auto spectrum = mean_field::solver::measureArnoldiSpectrum(
product, arnoldiDirection, communicator,
{.krylovDimension = arnoldiDimension,
.breakdownRelativeTolerance = 1.0e-13,
.ritzConvergenceRelativeTolerance = 1.0e-7,
.reorthogonalize = true}
);
REQUIRE(spectrum.achievedDimension > 32);
recordSpectrum("approximate_ldu_arnoldi_96", spectrum, gravityOperator.Width(), setupTime);
}

View File

@@ -0,0 +1,993 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <map>
#include <numbers>
#include <ranges>
#include <string>
#include <utility>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import mean_field;
import test_helpers;
namespace {
using Clock = std::chrono::steady_clock;
namespace backend = mean_field::preconditioning::backend;
namespace preconditioning = mean_field::preconditioning;
namespace solver = mean_field::solver;
struct MaterialBlockMeasurements final {
double density{0.0};
double surface{0.0};
double enthalpy{0.0};
};
[[nodiscard]] const char *buildConfiguration() noexcept {
#ifdef NDEBUG
return "release";
#else
return "debug";
#endif
}
[[nodiscard]] double maximumRankSeconds(
const Clock::time_point start,
const MPI_Comm communicator
) {
const double localSeconds = std::chrono::duration<double>(Clock::now() - start).count();
double maximumSeconds = 0.0;
MPI_Allreduce(&localSeconds, &maximumSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return maximumSeconds;
}
[[nodiscard]] double globalNorm(
const mfem::Vector &vector,
const 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(std::max(globalSquaredNorm, 0.0));
}
void announce(
const MPI_Comm communicator,
const std::string &message
) {
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "[P9 material-surface] " << message << std::endl;
}
}
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies makeDependencies() {
return {
.discretization = {.identity = 10103, .revision = 1},
.density = {.identity = 10111, .revision = 1},
.surfaceDeformation = {.identity = 10133, .revision = 1},
.gravityGradient = {.identity = 10139, .revision = 1},
.gravityPotential = {.identity = 10141, .revision = 1},
.enthalpy = {.identity = 10151, .revision = 1},
.bernoulliConstant = {.identity = 10159, .revision = 1},
.rotation = {.identity = 10163, .revision = 1},
.targetMass = {.identity = 10169, .revision = 1}
};
}
[[nodiscard]] mean_field::physics::RigidRotation zeroRotation() {
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
return {angularVelocity, center};
}
[[nodiscard]] mfem::Vector blockBalancedDirection(
const mfem::Array<int> &offsets,
const double phase,
const MPI_Comm communicator
) {
mfem::Vector direction(offsets.Last());
direction = 0.0;
for (int block = 0; block < offsets.Size() - 1; ++block) {
mfem::Vector values(direction, offsets[block], offsets[block + 1] - offsets[block]);
for (int index = 0; index < values.Size(); ++index) {
const double ordinal = static_cast<double>(index + 1);
values(index) = std::sin(0.371 * ordinal + phase + static_cast<double>(block)) +
0.29 * std::cos(0.173 * ordinal - 0.5 * phase);
}
const double norm = globalNorm(values, communicator);
REQUIRE(norm > 0.0);
values /= norm;
values.SyncAliasMemory(direction);
}
return direction;
}
[[nodiscard]] MaterialBlockMeasurements blockNorms(
const mfem::Vector &vector,
const mfem::Array<int> &offsets,
const MPI_Comm communicator
) {
REQUIRE(offsets.Size() == 4);
const mfem::Vector density(const_cast<mfem::real_t *>(vector.GetData()) + offsets[0], offsets[1] - offsets[0]);
const mfem::Vector surface(const_cast<mfem::real_t *>(vector.GetData()) + offsets[1], offsets[2] - offsets[1]);
const mfem::Vector enthalpy(const_cast<mfem::real_t *>(vector.GetData()) + offsets[2], offsets[3] - offsets[2]);
return {
.density = globalNorm(density, communicator),
.surface = globalNorm(surface, communicator),
.enthalpy = globalNorm(enthalpy, communicator)
};
}
[[nodiscard]] MaterialBlockMeasurements relativeBlockNorms(
const mfem::Vector &numerator,
const mfem::Vector &denominator,
const mfem::Array<int> &offsets,
const MPI_Comm communicator
) {
const MaterialBlockMeasurements numeratorNorms = blockNorms(numerator, offsets, communicator);
const MaterialBlockMeasurements denominatorNorms = blockNorms(denominator, offsets, communicator);
constexpr double floor = 1.0e-300;
return {
.density = numeratorNorms.density / std::max(denominatorNorms.density, floor),
.surface = numeratorNorms.surface / std::max(denominatorNorms.surface, floor),
.enthalpy = numeratorNorms.enthalpy / std::max(denominatorNorms.enthalpy, floor)
};
}
[[nodiscard]] std::map<
std::string,
std::string>
commonParameters(
const std::string &candidate,
const std::string &measurement,
const int dimension
) {
return {
{"build_configuration", buildConfiguration()},
{"candidate", candidate},
{"equation_of_state", "Polytrope(n=1)"},
{"experiment_schema", "p9_material_surface_v1"},
{"factorization", candidate},
{"linearization_state", "projected_lane_emden"},
{"measurement", measurement},
{"mesh_file", test_utils::setup_args().mesh_file},
{"operator", "restricted_material_surface_jacobian"},
{"preconditioned_product", "A_material_surface M^-1"},
{"root_dimension", std::to_string(dimension)},
{"rotation", "zero"}
};
}
void recordSpectrum(
const std::string &candidate,
const solver::ArnoldiSpectralMeasurement &spectrum,
const int dimension,
const double setupSeconds
) {
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_arnoldi_summary",
commonParameters(candidate, "arnoldi_summary", dimension),
{{"setup_seconds_maximum_rank", setupSeconds},
{"requested_dimension", static_cast<double>(spectrum.requestedDimension)},
{"achieved_dimension", static_cast<double>(spectrum.achievedDimension)},
{"invariant_subspace_found", spectrum.invariantSubspaceFound ? 1.0 : 0.0},
{"operator_applications", static_cast<double>(spectrum.operatorApplications)},
{"measurement_seconds_maximum_rank", spectrum.measurementSecondsMaximumRank},
{"operator_application_seconds_maximum_rank", spectrum.operatorApplicationSecondsMaximumRank},
{"projected_condition_proxy", spectrum.projectedConditionProxy},
{"projected_largest_singular_value", spectrum.projectedLargestSingularValue},
{"projected_smallest_singular_value", spectrum.projectedSmallestSingularValue},
{"centroid_real_part", spectrum.centroidRealPart},
{"centroid_imaginary_part", spectrum.centroidImaginaryPart},
{"rms_distance_from_one", spectrum.rmsDistanceFromOne},
{"rms_cluster_radius", spectrum.rmsClusterRadius},
{"minimum_magnitude", spectrum.minimumMagnitude},
{"maximum_magnitude", spectrum.maximumMagnitude},
{"minimum_real_part", spectrum.minimumRealPart},
{"maximum_real_part", spectrum.maximumRealPart},
{"maximum_absolute_imaginary_part", spectrum.maximumAbsoluteImaginaryPart},
{"negative_real_part_count", static_cast<double>(spectrum.negativeRealPartCount)},
{"converged_ritz_value_count", static_cast<double>(spectrum.convergedRitzValueCount)},
{"conjugate_pair_defect", spectrum.conjugatePairDefect},
{"projected_departure_from_normality", spectrum.projectedDepartureFromNormality},
{"field_of_values_minimum_real_part", spectrum.projectedFieldOfValuesMinimumRealPart},
{"field_of_values_maximum_real_part", spectrum.projectedFieldOfValuesMaximumRealPart}}
);
std::vector<solver::RitzValueMeasurement> ordered = spectrum.ritzValues;
std::ranges::sort(ordered, [](const auto &left, const auto &right) {
if (left.realPart != right.realPart) {
return left.realPart < right.realPart;
}
return left.imaginaryPart < right.imaginaryPart;
});
for (std::size_t index = 0; index < ordered.size(); ++index) {
const auto &value = ordered[index];
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_ritz_" + std::to_string(index),
commonParameters(candidate, "ritz_value", dimension),
{{"ritz_index", static_cast<double>(index)},
{"real_part", value.realPart},
{"imaginary_part", value.imaginaryPart},
{"magnitude", value.magnitude},
{"distance_from_one", value.distanceFromOne},
{"residual_estimate", value.residualEstimate},
{"relative_residual_estimate", value.relativeResidualEstimate},
{"converged", value.converged ? 1.0 : 0.0}}
);
}
}
template <typename Preconditioner>
void measureCandidate(
const std::string &candidate,
Preconditioner &inversePreconditioner,
const double setupSeconds,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const mfem::Vector &exactCorrection,
const mfem::Vector &rightHandSide,
const mfem::Vector &arnoldiDirection,
const MPI_Comm communicator,
std::map<
std::string,
double> preparationMetrics = {}
) {
constexpr int maximumIterations = 40;
constexpr int restartDimension = 20;
constexpr int arnoldiDimension = 16;
solver::InstrumentedOperator instrumentedOperation(operation);
solver::InstrumentedPreconditioner instrumentedPreconditioner(inversePreconditioner);
solver::ResidualHistoryMonitor monitor;
mfem::FGMRESSolver krylov(communicator);
krylov.SetPreconditioner(instrumentedPreconditioner);
krylov.SetOperator(instrumentedOperation);
krylov.SetMonitor(monitor);
krylov.SetRelTol(1.0e-8);
krylov.SetAbsTol(1.0e-12);
krylov.SetMaxIter(maximumIterations);
krylov.SetKDim(restartDimension);
krylov.SetPrintLevel(0);
mfem::Vector solution(operation.Width());
solution = 0.0;
announce(communicator, "solving manufactured system with " + candidate);
const Clock::time_point solveStart = Clock::now();
krylov.Mult(rightHandSide, solution);
const double solveSeconds = maximumRankSeconds(solveStart, communicator);
mfem::Vector trueResidual(operation.Height());
operation.Mult(solution, trueResidual);
trueResidual -= rightHandSide;
mfem::Vector solutionError(solution);
solutionError -= exactCorrection;
const double trueRelativeResidual =
globalNorm(trueResidual, communicator) /
std::max(globalNorm(rightHandSide, communicator), std::numeric_limits<double>::min());
const double relativeSolutionError =
globalNorm(solutionError, communicator) /
std::max(globalNorm(exactCorrection, communicator), std::numeric_limits<double>::min());
const MaterialBlockMeasurements relativeResidualBlocks =
relativeBlockNorms(trueResidual, rightHandSide, operation.GetOffsets(), communicator);
mfem::Vector preconditionedDirection(operation.Height());
inversePreconditioner.Mult(arnoldiDirection, preconditionedDirection);
mfem::Vector defect(operation.Height());
operation.Mult(preconditionedDirection, defect);
defect -= arnoldiDirection;
const MaterialBlockMeasurements defectBlocks = blockNorms(defect, operation.GetOffsets(), communicator);
const double defectNorm =
globalNorm(defect, communicator) /
std::max(globalNorm(arnoldiDirection, communicator), std::numeric_limits<double>::min());
std::map<std::string, double> solveMetrics{
{"setup_seconds_maximum_rank", setupSeconds},
{"maximum_iterations", static_cast<double>(maximumIterations)},
{"restart_dimension", static_cast<double>(restartDimension)},
{"solver_converged", krylov.GetConverged() ? 1.0 : 0.0},
{"outer_iterations", static_cast<double>(krylov.GetNumIterations())},
{"reported_initial_residual_norm", std::abs(krylov.GetInitialNorm())},
{"reported_final_residual_norm", std::abs(krylov.GetFinalNorm())},
{"true_relative_residual", trueRelativeResidual},
{"relative_solution_error", relativeSolutionError},
{"density_relative_residual", relativeResidualBlocks.density},
{"surface_relative_residual", relativeResidualBlocks.surface},
{"enthalpy_relative_residual", relativeResidualBlocks.enthalpy},
{"right_preconditioned_defect", defectNorm},
{"density_defect_norm", defectBlocks.density},
{"surface_defect_norm", defectBlocks.surface},
{"enthalpy_defect_norm", defectBlocks.enthalpy},
{"solve_seconds_maximum_rank", solveSeconds},
{"jacobian_applications", static_cast<double>(instrumentedOperation.GetStatistics().applications)},
{"jacobian_application_seconds", instrumentedOperation.GetStatistics().totalSeconds},
{"preconditioner_applications",
static_cast<double>(instrumentedPreconditioner.GetStatistics().applications)},
{"preconditioner_application_seconds", instrumentedPreconditioner.GetStatistics().totalSeconds},
{"preconditioner_maximum_application_seconds", instrumentedPreconditioner.GetStatistics().maximumSeconds}
};
solveMetrics.insert(preparationMetrics.begin(), preparationMetrics.end());
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_linear_solve",
commonParameters(candidate, "manufactured_linear_solve", operation.Width()), std::move(solveMetrics)
);
const double initialNorm = std::max(std::abs(krylov.GetInitialNorm()), 1.0e-300);
const auto &history = monitor.GetHistory();
for (std::size_t index = 0; index < history.size(); ++index) {
const auto &sample = history[index];
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_history_" + std::to_string(index),
commonParameters(candidate, "fgmres_residual_history", operation.Width()),
{{"history_sample", static_cast<double>(index)},
{"iteration", static_cast<double>(sample.iteration)},
{"reported_residual_norm", sample.reportedNorm},
{"reported_relative_residual", std::abs(sample.reportedNorm) / initialNorm},
{"final_measurement", sample.final ? 1.0 : 0.0}}
);
}
instrumentedOperation.ResetStatistics();
instrumentedPreconditioner.ResetStatistics();
solver::FixedRightPreconditionedOperator product(instrumentedOperation, instrumentedPreconditioner);
announce(communicator, "measuring " + candidate + " with 16-vector Arnoldi");
const solver::ArnoldiSpectralMeasurement spectrum = solver::measureArnoldiSpectrum(
product, arnoldiDirection, communicator,
{.krylovDimension = arnoldiDimension,
.breakdownRelativeTolerance = 1.0e-13,
.ritzConvergenceRelativeTolerance = 1.0e-7,
.reorthogonalize = true}
);
REQUIRE(std::isfinite(trueRelativeResidual));
REQUIRE(std::isfinite(relativeSolutionError));
REQUIRE(std::isfinite(defectNorm));
REQUIRE(std::isfinite(spectrum.projectedConditionProxy));
recordSpectrum(candidate, spectrum, operation.Width(), setupSeconds);
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "[P9 material-surface] " << candidate << ": iterations=" << krylov.GetNumIterations()
<< ", converged=" << (krylov.GetConverged() ? "yes" : "no")
<< ", true residual=" << trueRelativeResidual << ", defect=" << defectNorm
<< ", projected condition=" << spectrum.projectedConditionProxy << '\n';
}
}
template <preconditioning::MaterialSurfaceFactorizationPolicy Policy>
void prepareAndMeasure(
const std::string &candidate,
const Policy policy,
const auto &problem,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const mfem::Vector &exactCorrection,
const mfem::Vector &rightHandSide,
const mfem::Vector &arnoldiDirection,
const MPI_Comm communicator,
const preconditioning::MaterialSurfaceDiagonalOptions diagonalOptions = {}
) {
const Clock::time_point setupStart = Clock::now();
auto block = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::Diagonal{}, policy, diagonalOptions
);
auto prepared = preconditioning::prepare(problem, block);
const double setupTime = maximumRankSeconds(setupStart, communicator);
const auto &density = prepared.GetDensityDiagonalQuality();
const auto &surface = prepared.GetSurfaceDiagonalQuality();
const auto &enthalpy = prepared.GetEnthalpyDiagonalQuality();
const auto &calibration = prepared.GetSurfaceCalibration();
measureCandidate(
candidate, prepared, setupTime, operation, exactCorrection, rightHandSide, arnoldiDirection, communicator,
{{"density_diagonal_minimum", density.minimumAbsoluteEntryBeforeRegularization},
{"density_diagonal_maximum", density.maximumAbsoluteEntryBeforeRegularization},
{"density_diagonal_floor", density.appliedFloor},
{"density_regularized_entries", static_cast<double>(density.regularizedEntries)},
{"surface_diagonal_minimum", surface.minimumAbsoluteEntryBeforeRegularization},
{"surface_diagonal_maximum", surface.maximumAbsoluteEntryBeforeRegularization},
{"surface_diagonal_floor", surface.appliedFloor},
{"surface_regularized_entries", static_cast<double>(surface.regularizedEntries)},
{"surface_calibration_target", static_cast<double>(calibration.target)},
{"surface_calibration_probes", static_cast<double>(calibration.probeCount)},
{"surface_calibration_objective", static_cast<double>(calibration.objective)},
{"surface_calibration_scale", calibration.scale},
{"surface_calibration_inverse_multiplier", calibration.inverseMultiplier},
{"surface_calibration_numerator", calibration.leastSquaresNumerator},
{"surface_calibration_denominator", calibration.leastSquaresDenominator},
{"enthalpy_diagonal_minimum", enthalpy.minimumAbsoluteEntryBeforeRegularization},
{"enthalpy_diagonal_maximum", enthalpy.maximumAbsoluteEntryBeforeRegularization},
{"enthalpy_diagonal_floor", enthalpy.appliedFloor},
{"enthalpy_regularized_entries", static_cast<double>(enthalpy.regularizedEntries)}}
);
}
template <preconditioning::MaterialSurfaceFactorizationPolicy Policy>
void prepareAndMeasureH1(
const std::string &candidate,
const Policy policy,
const int fixedAMGCycles,
const int calibrationProbeCount,
const auto &problem,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const mfem::Vector &exactCorrection,
const mfem::Vector &rightHandSide,
const mfem::Vector &arnoldiDirection,
const MPI_Comm communicator
) {
REQUIRE(fixedAMGCycles > 0);
REQUIRE(calibrationProbeCount >= 3);
const Clock::time_point setupStart = Clock::now();
auto block = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::HypreBoomerAMG{backend::FixedCycles{.cycles = fixedAMGCycles}},
policy,
preconditioning::SurfaceH1MassStiffness{
.calibration = {
.target = preconditioning::SurfaceRieszCalibrationTarget::surface_jacobian,
.probeCount = calibrationProbeCount
}
}
);
auto prepared = preconditioning::prepare(problem, std::move(block));
const double setupTime = maximumRankSeconds(setupStart, communicator);
const auto &density = prepared.GetDensityDiagonalQuality();
const auto &enthalpy = prepared.GetEnthalpyDiagonalQuality();
const auto &fit = prepared.GetSurfaceFit();
measureCandidate(
candidate, prepared, setupTime, operation, exactCorrection, rightHandSide, arnoldiDirection, communicator,
{{"density_diagonal_minimum", density.minimumAbsoluteEntryBeforeRegularization},
{"density_diagonal_maximum", density.maximumAbsoluteEntryBeforeRegularization},
{"density_diagonal_floor", density.appliedFloor},
{"density_regularized_entries", static_cast<double>(density.regularizedEntries)},
{"surface_h1_calibration_target", static_cast<double>(fit.target)},
{"surface_h1_calibration_probes", static_cast<double>(fit.probeCount)},
{"surface_h1_fit_sign", fit.sign},
{"surface_h1_mass_coefficient", fit.massCoefficient},
{"surface_h1_stiffness_coefficient", fit.stiffnessCoefficient},
{"surface_h1_fit_relative_residual", fit.relativeResidual},
{"surface_h1_fit_relative_gram_determinant", fit.relativeGramDeterminant},
{"surface_amg_fixed_cycles", static_cast<double>(fixedAMGCycles)},
{"enthalpy_diagonal_minimum", enthalpy.minimumAbsoluteEntryBeforeRegularization},
{"enthalpy_diagonal_maximum", enthalpy.maximumAbsoluteEntryBeforeRegularization},
{"enthalpy_diagonal_floor", enthalpy.appliedFloor},
{"enthalpy_regularized_entries", static_cast<double>(enthalpy.regularizedEntries)}}
);
const auto &surfaceBackendStatistics = prepared.GetSurfaceBackend().GetStatistics();
const auto &factorizationStatistics = prepared.GetFactorization().GetStatistics();
const auto &preparationStatistics = prepared.GetStatistics();
auto parameters = commonParameters(candidate, "surface_h1_backend_statistics", operation.Width());
parameters["experiment_schema"] = "p9_material_surface_h1_v1";
parameters["surface_surrogate"] = "h1_mass_plus_tangential_stiffness";
parameters["surface_calibration_target"] = "surface_jacobian";
parameters["surface_calibration_probes"] = std::to_string(calibrationProbeCount);
parameters["surface_amg_fixed_cycles"] = std::to_string(fixedAMGCycles);
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_surface_h1_backend_statistics", std::move(parameters),
{{"setup_seconds_maximum_rank", setupTime},
{"surface_h1_fit_sign", fit.sign},
{"surface_h1_mass_coefficient", fit.massCoefficient},
{"surface_h1_stiffness_coefficient", fit.stiffnessCoefficient},
{"surface_h1_fit_relative_residual", fit.relativeResidual},
{"surface_h1_fit_relative_gram_determinant", fit.relativeGramDeterminant},
{"surface_backend_setups", static_cast<double>(surfaceBackendStatistics.setups)},
{"surface_backend_applications", static_cast<double>(surfaceBackendStatistics.applications)},
{"surface_backend_inner_iterations", static_cast<double>(surfaceBackendStatistics.innerIterations)},
{"surface_backend_last_inner_iterations",
static_cast<double>(surfaceBackendStatistics.lastInnerIterations)},
{"factorization_applications", static_cast<double>(factorizationStatistics.applications)},
{"surface_inverse_applications", static_cast<double>(factorizationStatistics.surfaceInverseApplications)},
{"block_setups", static_cast<double>(preparationStatistics.setups)},
{"surface_jacobian_probes", static_cast<double>(preparationStatistics.surfaceJacobianProbes)},
{"surface_h1_assemblies", static_cast<double>(preparationStatistics.surfaceH1Assemblies)}}
);
}
enum class SurfaceProbeMode { constant, ordered_low, alternating_high, deterministic_mixed };
[[nodiscard]] const char *surfaceProbeModeName(const SurfaceProbeMode mode) noexcept {
switch (mode) {
case SurfaceProbeMode::constant:
return "constant";
case SurfaceProbeMode::ordered_low:
return "ordered_low";
case SurfaceProbeMode::alternating_high:
return "alternating_high";
case SurfaceProbeMode::deterministic_mixed:
return "deterministic_mixed";
}
return "unknown";
}
[[nodiscard]] mfem::Vector normalizedSurfaceProbe(
const int localSize,
const SurfaceProbeMode mode,
const MPI_Comm communicator
) {
int globalSize = 0;
int offset = 0;
MPI_Allreduce(&localSize, &globalSize, 1, MPI_INT, MPI_SUM, communicator);
MPI_Exscan(&localSize, &offset, 1, MPI_INT, MPI_SUM, communicator);
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
offset = 0;
}
REQUIRE(globalSize > 0);
mfem::Vector probe(localSize);
for (int index = 0; index < localSize; ++index) {
const int globalIndex = offset + index;
const double position = (static_cast<double>(globalIndex) + 0.5) / static_cast<double>(globalSize);
switch (mode) {
case SurfaceProbeMode::constant:
probe(index) = 1.0;
break;
case SurfaceProbeMode::ordered_low:
probe(index) = std::cos(std::numbers::pi_v<double> * position);
break;
case SurfaceProbeMode::alternating_high:
probe(index) = globalIndex % 2 == 0 ? 1.0 : -1.0;
break;
case SurfaceProbeMode::deterministic_mixed:
probe(index) = 0.41 * std::cos(std::numbers::pi_v<double> * position) +
std::sin(5.0 * std::numbers::pi_v<double> * position) +
0.23 * (globalIndex % 2 == 0 ? 1.0 : -1.0);
break;
}
}
const double norm = globalNorm(probe, communicator);
REQUIRE(norm > 0.0);
probe /= norm;
return probe;
}
void applyParameterOverrides(
std::map<
std::string,
std::string> &parameters,
const std::map<
std::string,
std::string> &overrides
) {
for (const auto &[key, value] : overrides) {
parameters.insert_or_assign(key, value);
}
}
template <typename SurfaceInverse>
void recordSurfaceInverseRecovery(
const std::string &candidate,
SurfaceInverse &surfaceInverse,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const MPI_Comm communicator,
const std::map<
std::string,
std::string> &parameterOverrides
) {
constexpr std::array modes{
SurfaceProbeMode::constant, SurfaceProbeMode::ordered_low, SurfaceProbeMode::alternating_high,
SurfaceProbeMode::deterministic_mixed
};
const int surfaceSize = operation.GetOffsets()[2] - operation.GetOffsets()[1];
REQUIRE(surfaceInverse.Width() == surfaceSize);
REQUIRE(surfaceInverse.Height() == surfaceSize);
for (const SurfaceProbeMode mode : modes) {
const mfem::Vector probe = normalizedSurfaceProbe(surfaceSize, mode, communicator);
mfem::Vector surfaceAction(surfaceSize);
mfem::Vector recovered(surfaceSize);
operation.ApplySurfaceToSurface(probe, surfaceAction);
const Clock::time_point inverseStart = Clock::now();
surfaceInverse.Mult(surfaceAction, recovered);
const double inverseSeconds = maximumRankSeconds(inverseStart, communicator);
mfem::Vector recoveryError(recovered);
recoveryError -= probe;
const double probeNorm = globalNorm(probe, communicator);
const double actionNorm = globalNorm(surfaceAction, communicator);
const double recoveredNorm = globalNorm(recovered, communicator);
const double relativeError = globalNorm(recoveryError, communicator) / probeNorm;
constexpr double nonzeroFloor = 1.0e-300;
auto parameters = commonParameters(candidate, "surface_inverse_recovery", operation.Width());
applyParameterOverrides(parameters, parameterOverrides);
parameters["surface_probe_mode"] = surfaceProbeModeName(mode);
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_" + surfaceProbeModeName(mode),
std::move(parameters),
{{"surface_probe_norm", probeNorm},
{"surface_action_norm", actionNorm},
{"surface_recovered_norm", recoveredNorm},
{"surface_recovery_relative_error", relativeError},
{"surface_operator_gain", actionNorm / probeNorm},
{"surface_inverse_gain", recoveredNorm / std::max(actionNorm, nonzeroFloor)},
{"surface_recovered_gain", recoveredNorm / probeNorm},
{"surface_inverse_seconds_maximum_rank", inverseSeconds}}
);
}
}
template <typename PreparedPreconditioner>
void recordBalancedPreconditionedDefect(
const std::string &candidate,
PreparedPreconditioner &prepared,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const MPI_Comm communicator,
const std::map<
std::string,
std::string> &parameterOverrides
) {
const mfem::Vector direction = blockBalancedDirection(operation.GetOffsets(), 1.37, communicator);
mfem::Vector correction(operation.Width());
mfem::Vector defect(operation.Height());
const Clock::time_point applicationStart = Clock::now();
prepared.Mult(direction, correction);
const double applicationSeconds = maximumRankSeconds(applicationStart, communicator);
operation.Mult(correction, defect);
defect -= direction;
const MaterialBlockMeasurements blockDefects = blockNorms(defect, operation.GetOffsets(), communicator);
const double relativeDefect = globalNorm(defect, communicator) /
std::max(globalNorm(direction, communicator), std::numeric_limits<double>::min());
auto parameters = commonParameters(candidate, "balanced_right_preconditioned_defect", operation.Width());
applyParameterOverrides(parameters, parameterOverrides);
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_balanced_defect", std::move(parameters),
{{"right_preconditioned_defect", relativeDefect},
{"density_defect_norm", blockDefects.density},
{"surface_defect_norm", blockDefects.surface},
{"enthalpy_defect_norm", blockDefects.enthalpy},
{"preconditioner_application_seconds_maximum_rank", applicationSeconds}}
);
}
void measureH1CalibrationFloor(
const std::string &candidate,
const std::string &relativeMassCoefficientFloorLabel,
const double relativeMassCoefficientFloor,
const auto &problem,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const MPI_Comm communicator
) {
const Clock::time_point setupStart = Clock::now();
auto block = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::HypreBoomerAMG{backend::FixedCycles{.cycles = 1}},
preconditioning::ApproximateMaterialSurfaceLDU{},
preconditioning::SurfaceH1MassStiffness{
.calibration =
{.target = preconditioning::SurfaceRieszCalibrationTarget::surface_jacobian, .probeCount = 4},
.relativeMassCoefficientFloor = relativeMassCoefficientFloor
}
);
auto prepared = preconditioning::prepare(problem, std::move(block));
const double setupTime = maximumRankSeconds(setupStart, communicator);
const auto &fit = prepared.GetSurfaceFit();
const std::map<std::string, std::string> parameters{
{"experiment_schema", "p9_material_surface_h1_tuning_v1"},
{"surface_surrogate", "h1_mass_plus_tangential_stiffness"},
{"surface_calibration_target", "surface_jacobian"},
{"surface_calibration_probes", "4"},
{"surface_amg_fixed_cycles", "1"},
{"relative_mass_coefficient_floor", relativeMassCoefficientFloorLabel}
};
recordSurfaceInverseRecovery(candidate, prepared.GetSurfaceInverse(), operation, communicator, parameters);
recordBalancedPreconditionedDefect(candidate, prepared, operation, communicator, parameters);
const auto &backendStatistics = prepared.GetSurfaceBackend().GetStatistics();
const auto &factorizationStatistics = prepared.GetFactorization().GetStatistics();
const auto &preparationStatistics = prepared.GetStatistics();
auto summaryParameters = commonParameters(candidate, "surface_h1_floor_summary", operation.Width());
applyParameterOverrides(summaryParameters, parameters);
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_summary", std::move(summaryParameters),
{{"setup_seconds_maximum_rank", setupTime},
{"relative_mass_coefficient_floor", relativeMassCoefficientFloor},
{"surface_h1_fit_sign", fit.sign},
{"surface_h1_mass_coefficient", fit.massCoefficient},
{"surface_h1_stiffness_coefficient", fit.stiffnessCoefficient},
{"surface_h1_fit_relative_residual", fit.relativeResidual},
{"surface_h1_fit_relative_gram_determinant", fit.relativeGramDeterminant},
{"surface_backend_setups", static_cast<double>(backendStatistics.setups)},
{"surface_backend_applications", static_cast<double>(backendStatistics.applications)},
{"surface_backend_inner_iterations", static_cast<double>(backendStatistics.innerIterations)},
{"surface_backend_last_inner_iterations", static_cast<double>(backendStatistics.lastInnerIterations)},
{"factorization_applications", static_cast<double>(factorizationStatistics.applications)},
{"surface_inverse_applications", static_cast<double>(factorizationStatistics.surfaceInverseApplications)},
{"surface_jacobian_probes", static_cast<double>(preparationStatistics.surfaceJacobianProbes)},
{"surface_h1_assemblies", static_cast<double>(preparationStatistics.surfaceH1Assemblies)}}
);
}
void measureScalarSurfaceControl(
const std::string &candidate,
const preconditioning::SurfaceRieszCalibrationObjective objective,
const int calibrationProbeCount,
const auto &problem,
const mean_field::preconditioning::MaterialSurfaceJacobianOperator &operation,
const MPI_Comm communicator
) {
REQUIRE(calibrationProbeCount > 0);
const preconditioning::MaterialSurfaceDiagonalOptions calibration{
.surfaceCalibration = {
.target = preconditioning::SurfaceRieszCalibrationTarget::surface_jacobian,
.probeCount = calibrationProbeCount,
.objective = objective
}
};
const Clock::time_point setupStart = Clock::now();
auto block = preconditioning::materialSurfaceBlock(
problem, backend::Diagonal{}, backend::Diagonal{}, preconditioning::ApproximateMaterialSurfaceLDU{},
calibration
);
auto prepared = preconditioning::prepare(problem, std::move(block));
auto directSurfaceInverse = backend::prepare(backend::Diagonal{}, prepared.GetSurfaceDiagonal());
const double setupTime = maximumRankSeconds(setupStart, communicator);
const auto &calibrationData = prepared.GetSurfaceCalibration();
const std::map<std::string, std::string> parameters{
{"experiment_schema", "p9_material_surface_h1_tuning_v1"},
{"surface_surrogate", "scalar_mass_diagonal"},
{"surface_calibration_target", "surface_jacobian"},
{"surface_calibration_probes", std::to_string(calibrationProbeCount)},
{"surface_calibration_objective",
objective == preconditioning::SurfaceRieszCalibrationObjective::operator_action
? "operator_action"
: "right_preconditioned_action"}
};
recordSurfaceInverseRecovery(candidate, directSurfaceInverse, operation, communicator, parameters);
recordBalancedPreconditionedDefect(candidate, prepared, operation, communicator, parameters);
const auto &directStatistics = directSurfaceInverse.GetStatistics();
const auto &factorizationStatistics = prepared.GetFactorization().GetStatistics();
auto summaryParameters = commonParameters(candidate, "scalar_surface_control_summary", operation.Width());
applyParameterOverrides(summaryParameters, parameters);
experiment::record_experiment_result(
"material_surface_preconditioning_p9", candidate + "_summary", std::move(summaryParameters),
{{"setup_seconds_maximum_rank", setupTime},
{"surface_calibration_scale", calibrationData.scale},
{"surface_calibration_inverse_multiplier", calibrationData.inverseMultiplier},
{"surface_calibration_numerator", calibrationData.leastSquaresNumerator},
{"surface_calibration_denominator", calibrationData.leastSquaresDenominator},
{"surface_backend_setups", static_cast<double>(directStatistics.setups)},
{"surface_backend_applications", static_cast<double>(directStatistics.applications)},
{"factorization_applications", static_cast<double>(factorizationStatistics.applications)},
{"surface_inverse_applications", static_cast<double>(factorizationStatistics.surfaceInverseApplications)}}
);
}
} // namespace
TEST_CASE(
"Material Surface P9 Numerical Factorization Comparison",
"[preconditioning][material_surface][diagnostics][experiment][spectrum][p9][p9_baseline]"
) {
using namespace mean_field;
const utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const MPI_Comm communicator = finiteElements.mesh->GetComm();
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
preconditioning::MaterialSurfaceJacobianOperator materialSurfaceOperator(physical);
const auto exactCorrection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.23, communicator);
mfem::Vector rightHandSide(materialSurfaceOperator.Height());
materialSurfaceOperator.Mult(exactCorrection, rightHandSide);
const auto arnoldiDirection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.79, communicator);
solver::IdentityPreconditioner identity(materialSurfaceOperator.Width());
measureCandidate(
"identity", identity, 0.0, materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection,
communicator
);
prepareAndMeasure(
"block_diagonal", preconditioning::MaterialSurfaceBlockDiagonal{}, problem, materialSurfaceOperator,
exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasure(
"material_independent_surface", preconditioning::CoupledMaterialIndependentSurface{}, problem,
materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasure(
"material_then_surface", preconditioning::MaterialThenSurfaceTriangular{}, problem, materialSurfaceOperator,
exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasure(
"surface_then_material", preconditioning::SurfaceThenMaterialTriangular{}, problem, materialSurfaceOperator,
exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasure(
"approximate_ldu", preconditioning::ApproximateMaterialSurfaceLDU{}, problem, materialSurfaceOperator,
exactCorrection, rightHandSide, arnoldiDirection, communicator
);
}
TEST_CASE(
"Material Surface P9 Calibrated LDU Comparison",
"[preconditioning][material_surface][diagnostics][experiment][spectrum][p9][p9_refinement]"
) {
using namespace mean_field;
const utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const MPI_Comm communicator = finiteElements.mesh->GetComm();
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
preconditioning::MaterialSurfaceJacobianOperator materialSurfaceOperator(physical);
const auto exactCorrection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.23, communicator);
mfem::Vector rightHandSide(materialSurfaceOperator.Height());
materialSurfaceOperator.Mult(exactCorrection, rightHandSide);
const auto arnoldiDirection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.79, communicator);
constexpr preconditioning::MaterialSurfaceDiagonalOptions surfaceJacobianCalibration{
.surfaceCalibration = {
.target = preconditioning::SurfaceRieszCalibrationTarget::surface_jacobian, .probeCount = 4
}
};
constexpr preconditioning::MaterialSurfaceDiagonalOptions surfaceSchurCalibration{
.surfaceCalibration = {
.target = preconditioning::SurfaceRieszCalibrationTarget::approximate_material_schur, .probeCount = 4
}
};
prepareAndMeasure(
"surface_then_material_calibrated_aqq", preconditioning::SurfaceThenMaterialTriangular{}, problem,
materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator,
surfaceJacobianCalibration
);
prepareAndMeasure(
"approximate_ldu", preconditioning::ApproximateMaterialSurfaceLDU{}, problem, materialSurfaceOperator,
exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasure(
"approximate_ldu_calibrated_aqq", preconditioning::ApproximateMaterialSurfaceLDU{}, problem,
materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator,
surfaceJacobianCalibration
);
prepareAndMeasure(
"approximate_ldu_calibrated_schur", preconditioning::ApproximateMaterialSurfaceLDU{}, problem,
materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator, surfaceSchurCalibration
);
}
TEST_CASE(
"Material Surface P9 Frequency-Aware Surface Refinement",
"[preconditioning][material_surface][diagnostics][experiment][spectrum][p9][p9_h1_refinement]"
) {
using namespace mean_field;
const utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const MPI_Comm communicator = finiteElements.mesh->GetComm();
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
preconditioning::MaterialSurfaceJacobianOperator materialSurfaceOperator(physical);
const auto exactCorrection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.23, communicator);
mfem::Vector rightHandSide(materialSurfaceOperator.Height());
materialSurfaceOperator.Mult(exactCorrection, rightHandSide);
const auto arnoldiDirection = blockBalancedDirection(materialSurfaceOperator.GetOffsets(), 0.79, communicator);
constexpr int calibrationProbeCount = 4;
prepareAndMeasureH1(
"h1_aqq_surface_then_material_amg1", preconditioning::SurfaceThenMaterialTriangular{}, 1, calibrationProbeCount,
problem, materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureH1(
"h1_aqq_approximate_ldu_amg1", preconditioning::ApproximateMaterialSurfaceLDU{}, 1, calibrationProbeCount,
problem, materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator
);
prepareAndMeasureH1(
"h1_aqq_approximate_ldu_amg2", preconditioning::ApproximateMaterialSurfaceLDU{}, 2, calibrationProbeCount,
problem, materialSurfaceOperator, exactCorrection, rightHandSide, arnoldiDirection, communicator
);
}
TEST_CASE(
"Material Surface P9 H1 Calibration Floor Tuning",
"[preconditioning][material_surface][diagnostics][experiment][p9][p9_h1_tuning]"
) {
using namespace mean_field;
const utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const MPI_Comm communicator = finiteElements.mesh->GetComm();
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 1024}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();
preconditioning::MaterialSurfaceJacobianOperator materialSurfaceOperator(physical);
constexpr std::array floorCases{
std::pair{"1e-10", 1.0e-10}, std::pair{"1e-4", 1.0e-4}, std::pair{"1e-2", 1.0e-2}, std::pair{"1e-1", 1.0e-1},
std::pair{"1", 1.0}
};
for (const auto &[label, floor] : floorCases) {
measureH1CalibrationFloor(
std::string("h1_aqq_floor_") + label, label, floor, problem, materialSurfaceOperator, communicator
);
}
measureScalarSurfaceControl(
"scalar_aqq_operator_calibrated_diagonal_control",
preconditioning::SurfaceRieszCalibrationObjective::operator_action, 4, problem, materialSurfaceOperator,
communicator
);
constexpr std::array inverseProbeCounts{1, 2, 4, 8, 16};
for (const int probeCount : inverseProbeCounts) {
measureScalarSurfaceControl(
"scalar_aqq_right_calibrated_diagonal_" + std::to_string(probeCount) + "_probes",
preconditioning::SurfaceRieszCalibrationObjective::right_preconditioned_action, probeCount, problem,
materialSurfaceOperator, communicator
);
}
}

View File

@@ -0,0 +1,454 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <map>
#include <numbers>
#include <ranges>
#include <span>
#include <string>
#include <utility>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <mpi.h>
import mean_field;
import test_helpers;
import experiment;
namespace {
using Clock = std::chrono::steady_clock;
[[nodiscard]] const char *build_configuration() noexcept {
#ifdef NDEBUG
return "release";
#else
return "debug";
#endif
}
[[nodiscard]] double maximum_rank_seconds(
const Clock::time_point start,
const MPI_Comm communicator
) {
const double localSeconds = std::chrono::duration<double>(Clock::now() - start).count();
double maximumSeconds{0.0};
MPI_Allreduce(&localSeconds, &maximumSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return maximumSeconds;
}
void announce(
const MPI_Comm communicator,
const std::string &message
) {
int rank{0};
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << message << std::endl;
}
}
class ArnoldiProgressOperator final : public mfem::Operator {
public:
ArnoldiProgressOperator(
const mfem::Operator &operation,
const MPI_Comm communicator,
const int expectedApplications,
const int reportingInterval
)
: mfem::Operator(
operation.Height(),
operation.Width()
),
m_operation(&operation),
m_communicator(communicator),
m_expectedApplications(expectedApplications),
m_reportingInterval(reportingInterval) {
}
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
m_operation->Mult(input, output);
++m_completedApplications;
if (m_completedApplications == 1 || m_completedApplications == m_expectedApplications ||
m_completedApplications % m_reportingInterval == 0) {
announce(
m_communicator, "Arnoldi progress: " + std::to_string(m_completedApplications) + "/" +
std::to_string(m_expectedApplications) + " Jacobian applications"
);
}
}
private:
const mfem::Operator *m_operation;
MPI_Comm m_communicator;
int m_expectedApplications;
int m_reportingInterval;
mutable int m_completedApplications{0};
};
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 8101, .revision = 1},
.density = {.identity = 8103, .revision = 1},
.surfaceDeformation = {.identity = 8107, .revision = 1},
.gravityGradient = {.identity = 8111, .revision = 1},
.gravityPotential = {.identity = 8117, .revision = 1},
.enthalpy = {.identity = 8123, .revision = 1},
.bernoulliConstant = {.identity = 8129, .revision = 1},
.rotation = {.identity = 8131, .revision = 1},
.targetMass = {.identity = 8137, .revision = 1}
};
}
[[nodiscard]] mean_field::physics::RigidRotation make_zero_rotation() {
mfem::Vector angularVelocity(3);
mfem::Vector center(3);
angularVelocity = 0.0;
center = 0.0;
return {angularVelocity, center};
}
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
const 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(std::max(globalSquaredNorm, 0.0));
}
[[nodiscard]] mfem::Vector make_block_balanced_direction(
const int stateSize,
const std::span<const mean_field::operators::RootBlockDescriptor> valueBlocks,
const MPI_Comm communicator
) {
mfem::Vector direction(stateSize);
direction = 0.0;
for (const mean_field::operators::RootBlockDescriptor &block : valueBlocks) {
mfem::Vector values(direction.GetData() + block.offset, block.size);
for (int index = 0; index < values.Size(); ++index) {
const double ordinal = static_cast<double>(block.canonicalIndex + 1);
values(index) = std::sin(0.6180339887498948 * static_cast<double>(index + 1) + ordinal);
}
const double norm = global_norm(values, communicator);
if (norm > 0.0) {
values /= norm;
}
}
return direction;
}
void require_finite(const double value) {
REQUIRE(std::isfinite(value));
}
[[nodiscard]] std::map<
std::string,
std::string>
common_parameters(
const std::string &measurement,
const int stateSize
) {
return {
{"build_configuration", build_configuration()},
{"equation_of_state", "Polytrope(n=3)"},
{"experiment_schema", "p0_extended_v2"},
{"linearization_state", "projected_lane_emden"},
{"measurement", measurement},
{"mesh_file", test_utils::setup_args().mesh_file},
{"preconditioner", "identity"},
{"preconditioned_product", "J M^-1"},
{"root_dimension", std::to_string(stateSize)}
};
}
} // namespace
TEST_CASE(
"Stellar Equilibrium P0 Identity Preconditioning Baseline",
"[preconditioning][diagnostics][baseline][spectrum]"
) {
using namespace mean_field;
constexpr int arnoldiDimension = 48;
const MPI_Comm world = MPI_COMM_WORLD;
const Clock::time_point experimentStart = Clock::now();
announce(world, "P0 extended baseline: constructing the finite-element discretization");
const Clock::time_point finiteElementSetupStart = Clock::now();
utils::Args args = test_utils::setup_args();
fem::FEM finiteElementModel = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(finiteElementModel.okay());
const MPI_Comm communicator = finiteElementModel.mesh->GetComm();
const double finiteElementSetupSeconds = maximum_rank_seconds(finiteElementSetupStart, communicator);
announce(
communicator, "P0 extended baseline: finite-element setup completed in " +
std::to_string(finiteElementSetupSeconds) + " seconds"
);
constexpr double stellarRadius = utils::RADIUS;
constexpr double targetMass = utils::MASS;
const Clock::time_point calibrationStart = Clock::now();
const seed::DimensionlessLaneEmdenSolution dimensionlessProfile = seed::integrateLaneEmden(3.0, 10.0);
REQUIRE(dimensionlessProfile.firstZeroCoordinate.has_value());
const double surfaceCoordinate = *dimensionlessProfile.firstZeroCoordinate;
const double surfaceDerivative =
dimensionlessProfile.thetaDerivative(dimensionlessProfile.thetaDerivative.Size() - 1);
const double dimensionlessMass = -surfaceCoordinate * surfaceCoordinate * surfaceDerivative;
REQUIRE(dimensionlessMass > 0.0);
const double massScale = targetMass / (4.0 * std::numbers::pi_v<double> * dimensionlessMass);
const double polytropicConstant = std::numbers::pi_v<double> * utils::G * std::pow(massScale, 2.0 / 3.0);
const double radialScale = stellarRadius / surfaceCoordinate;
const double centralDensity =
std::pow(polytropicConstant / (std::numbers::pi_v<double> * utils::G * radialScale * radialScale), 1.5);
const double calibrationSeconds = maximum_rank_seconds(calibrationStart, communicator);
const Clock::time_point problemConstructionStart = Clock::now();
const auto stellarModel = model::StellarModel(
eos::Polytrope({.n = 3.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
const double problemConstructionSeconds = maximum_rank_seconds(problemConstructionStart, communicator);
announce(communicator, "P0 extended baseline: projecting the Lane-Emden seed");
const Clock::time_point seedProjectionStart = Clock::now();
const auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 4096}));
const double seedProjectionSeconds = maximum_rank_seconds(seedProjectionStart, communicator);
announce(communicator, "P0 extended baseline: preparing the complete equilibrium operator");
const Clock::time_point operatorPreparationStart = Clock::now();
const auto preparation = problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
REQUIRE(preparation.assembledResidual);
const double operatorPreparationSeconds = maximum_rank_seconds(operatorPreparationStart, communicator);
const mfem::Operator &rawJacobian = problem.GetLinearizationOperator();
mfem::Vector knownDirection =
make_block_balanced_direction(problem.StateSize(), problem.GetManifest().valueBlocks(), communicator);
mfem::Vector rightHandSide(problem.EquationSize());
const Clock::time_point applicationStart = Clock::now();
rawJacobian.Mult(knownDirection, rightHandSide);
const double applicationSeconds = maximum_rank_seconds(applicationStart, communicator);
REQUIRE(rightHandSide.Size() == problem.EquationSize());
require_finite(global_norm(rightHandSide, communicator));
announce(
communicator, "P0 extended baseline: first prepared Jacobian application completed in " +
std::to_string(applicationSeconds) + " seconds"
);
if (std::getenv("MEANFIELD_SINGLE_JACOBIAN_BENCHMARK") != nullptr) {
int rank{0};
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "Single prepared Jacobian application: " << applicationSeconds << " seconds\n";
}
return;
}
solver::IdentityPreconditioner identity(problem.StateSize());
solver::InstrumentedOperator instrumentedJacobian(rawJacobian);
solver::InstrumentedPreconditioner instrumentedPreconditioner(identity);
solver::ResidualHistoryMonitor monitor;
mfem::FGMRESSolver krylov(communicator);
krylov.SetPreconditioner(instrumentedPreconditioner);
krylov.SetOperator(instrumentedJacobian);
krylov.SetMonitor(monitor);
krylov.SetRelTol(1.0e-8);
krylov.SetAbsTol(1.0e-12);
krylov.SetMaxIter(40);
krylov.SetKDim(20);
krylov.SetPrintLevel(1);
mfem::Vector solution(problem.StateSize());
solution = 0.0;
const operators::PreparedStellarEquilibriumStatistics statisticsBeforeSolve =
problem.GetPreparedOperator().GetPhysicalOperator().GetStatistics();
announce(communicator, "P0 extended baseline: starting the 40-iteration identity-preconditioned FGMRES solve");
const Clock::time_point solveStart = Clock::now();
krylov.Mult(rightHandSide, solution);
const double localSolveSeconds = std::chrono::duration<double>(Clock::now() - solveStart).count();
const operators::PreparedStellarEquilibriumStatistics statisticsAfterSolve =
problem.GetPreparedOperator().GetPhysicalOperator().GetStatistics();
announce(communicator, "P0 extended baseline: independently reconstructing the true residual");
const Clock::time_point directResidualStart = Clock::now();
const solver::LinearSolveMeasurement solveMeasurement = solver::measureLinearSolve(
krylov, rawJacobian, rightHandSide, solution, problem.GetManifest().residualBlocks(),
instrumentedJacobian.GetStatistics(), instrumentedPreconditioner.GetStatistics(),
instrumentedPreconditioner.GetLifecycleStatistics(), monitor, localSolveSeconds, communicator
);
const double directResidualMeasurementSeconds = maximum_rank_seconds(directResidualStart, communicator);
require_finite(solveMeasurement.directResidual.relativeResidual);
require_finite(solveMeasurement.solveSecondsMaximumRank);
std::map<std::string, double> solveMetrics{
{"solver_converged", solveMeasurement.solverConverged ? 1.0 : 0.0},
{"outer_iterations", static_cast<double>(solveMeasurement.outerIterations)},
{"reported_initial_residual_norm", solveMeasurement.solverReportedInitialNorm},
{"reported_final_residual_norm", solveMeasurement.solverReportedFinalNorm},
{"reported_residual_reduction", solveMeasurement.solverReportedResidualReduction},
{"true_residual_norm", solveMeasurement.directResidual.trueResidualNorm},
{"true_relative_residual", solveMeasurement.directResidual.relativeResidual},
{"rhs_norm", solveMeasurement.directResidual.rightHandSideNorm},
{"true_residual_digits_per_jacobian_application",
solveMeasurement.trueResidualDigitsReducedPerJacobianApplication},
{"finite_element_setup_seconds", finiteElementSetupSeconds},
{"lane_emden_calibration_seconds", calibrationSeconds},
{"equilibrium_problem_construction_seconds", problemConstructionSeconds},
{"seed_projection_seconds", seedProjectionSeconds},
{"operator_preparation_seconds", operatorPreparationSeconds},
{"initial_jacobian_application_seconds", applicationSeconds},
{"direct_residual_measurement_seconds", directResidualMeasurementSeconds},
{"solve_seconds_maximum_rank", solveMeasurement.solveSecondsMaximumRank},
{"jacobian_applications", static_cast<double>(solveMeasurement.jacobian.applications)},
{"jacobian_application_seconds", solveMeasurement.jacobian.totalSeconds},
{"jacobian_maximum_application_seconds", solveMeasurement.jacobian.maximumSeconds},
{"inverse_preconditioner_applications",
static_cast<double>(solveMeasurement.inversePreconditioner.applications)},
{"inverse_preconditioner_application_seconds", solveMeasurement.inversePreconditioner.totalSeconds},
{"inverse_preconditioner_maximum_application_seconds", solveMeasurement.inversePreconditioner.maximumSeconds},
{"inverse_preconditioner_setups", static_cast<double>(solveMeasurement.inversePreconditionerLifecycle.setups)},
{"inverse_preconditioner_refreshes",
static_cast<double>(solveMeasurement.inversePreconditionerLifecycle.refreshes)},
{"inverse_preconditioner_setup_seconds", solveMeasurement.inversePreconditionerLifecycle.setupSeconds},
{"inverse_preconditioner_refresh_seconds", solveMeasurement.inversePreconditionerLifecycle.refreshSeconds},
{"prepared_residual_assemblies_during_solve",
static_cast<double>(statisticsAfterSolve.residualAssemblies - statisticsBeforeSolve.residualAssemblies)},
{"prepared_geometry_builds_during_solve",
static_cast<double>(
statisticsAfterSolve.generatedGeometryBuilds - statisticsBeforeSolve.generatedGeometryBuilds
)},
{"prepared_jacobian_applications_during_solve",
static_cast<double>(statisticsAfterSolve.jacobianApplications - statisticsBeforeSolve.jacobianApplications)}
};
for (const solver::ResidualBlockMeasurement &block : solveMeasurement.directResidual.blocks) {
const std::string prefix = "residual_block." + block.stableId;
solveMetrics[prefix + ".descriptor_scale"] = block.descriptorScale;
solveMetrics[prefix + ".rhs_norm"] = block.rightHandSideNorm;
solveMetrics[prefix + ".true_norm"] = block.trueResidualNorm;
solveMetrics[prefix + ".block_relative_residual"] = block.blockRelativeResidual;
solveMetrics[prefix + ".scaled_rhs_norm"] = block.scaledRightHandSideNorm;
solveMetrics[prefix + ".scaled_true_norm"] = block.scaledTrueResidualNorm;
solveMetrics[prefix + ".fraction_global_squared_residual"] = block.fractionOfGlobalSquaredResidualNorm;
solveMetrics[prefix + ".global_relative_contribution"] = block.contributionToGlobalRelativeResidual;
}
experiment::record_experiment_result(
"stellar_preconditioning_p0", "identity_linear_solve", common_parameters("linear_solve", problem.StateSize()),
std::move(solveMetrics)
);
const double reportedInitialDenominator = std::max(solveMeasurement.solverReportedInitialNorm, 1.0e-300);
for (std::size_t sample = 0; sample < solveMeasurement.reportedResidualHistory.size(); ++sample) {
const solver::IterationResidualMeasurement &residual = solveMeasurement.reportedResidualHistory[sample];
experiment::record_experiment_result(
"stellar_preconditioning_p0", "identity_fgmres_history_" + std::to_string(sample),
common_parameters("fgmres_residual_history", problem.StateSize()),
{{"history_sample", static_cast<double>(sample)},
{"iteration", static_cast<double>(residual.iteration)},
{"reported_residual_norm", residual.reportedNorm},
{"reported_relative_residual", residual.reportedNorm / reportedInitialDenominator},
{"final_measurement", residual.final ? 1.0 : 0.0}}
);
}
instrumentedJacobian.ResetStatistics();
instrumentedPreconditioner.ResetStatistics();
solver::FixedRightPreconditionedOperator rightPreconditionedProduct(
instrumentedJacobian, instrumentedPreconditioner
);
ArnoldiProgressOperator progressOperator(rightPreconditionedProduct, communicator, arnoldiDimension, 4);
announce(
communicator,
"P0 extended baseline: starting the " + std::to_string(arnoldiDimension) + "-vector Arnoldi measurement"
);
const solver::ArnoldiSpectralMeasurement spectrum = solver::measureArnoldiSpectrum(
progressOperator, knownDirection, communicator,
{.krylovDimension = arnoldiDimension,
.breakdownRelativeTolerance = 1.0e-13,
.ritzConvergenceRelativeTolerance = 1.0e-7,
.reorthogonalize = true}
);
require_finite(spectrum.projectedLargestSingularValue);
require_finite(spectrum.centroidRealPart);
require_finite(spectrum.rmsClusterRadius);
experiment::record_experiment_result(
"stellar_preconditioning_p0", "identity_arnoldi_summary",
common_parameters("arnoldi_summary", problem.StateSize()),
{{"requested_krylov_dimension", static_cast<double>(spectrum.requestedDimension)},
{"achieved_krylov_dimension", static_cast<double>(spectrum.achievedDimension)},
{"invariant_subspace_found", spectrum.invariantSubspaceFound ? 1.0 : 0.0},
{"operator_applications", static_cast<double>(spectrum.operatorApplications)},
{"arnoldi_operator_application_seconds", spectrum.operatorApplicationSecondsMaximumRank},
{"arnoldi_operator_maximum_application_seconds", spectrum.operatorMaximumApplicationSecondsMaximumRank},
{"arnoldi_measurement_seconds", spectrum.measurementSecondsMaximumRank},
{"arnoldi_nonapplication_seconds", spectrum.nonApplicationSecondsMaximumRank},
{"experiment_elapsed_through_arnoldi_seconds", maximum_rank_seconds(experimentStart, communicator)},
{"converged_ritz_values", static_cast<double>(spectrum.convergedRitzValueCount)},
{"negative_real_part_ritz_values", static_cast<double>(spectrum.negativeRealPartCount)},
{"projected_largest_singular_value", spectrum.projectedLargestSingularValue},
{"projected_smallest_singular_value", spectrum.projectedSmallestSingularValue},
{"projected_condition_proxy", spectrum.projectedConditionProxy},
{"ritz_centroid_real", spectrum.centroidRealPart},
{"ritz_centroid_imaginary", spectrum.centroidImaginaryPart},
{"ritz_rms_distance_from_one", spectrum.rmsDistanceFromOne},
{"ritz_rms_cluster_radius", spectrum.rmsClusterRadius},
{"ritz_minimum_magnitude", spectrum.minimumMagnitude},
{"ritz_maximum_magnitude", spectrum.maximumMagnitude},
{"ritz_minimum_real_part", spectrum.minimumRealPart},
{"ritz_maximum_real_part", spectrum.maximumRealPart},
{"ritz_maximum_absolute_imaginary_part", spectrum.maximumAbsoluteImaginaryPart},
{"ritz_conjugate_pair_defect", spectrum.conjugatePairDefect},
{"projected_departure_from_normality", spectrum.projectedDepartureFromNormality},
{"projected_field_of_values_minimum_real_part", spectrum.projectedFieldOfValuesMinimumRealPart},
{"projected_field_of_values_maximum_real_part", spectrum.projectedFieldOfValuesMaximumRealPart},
{"measured_jacobian_applications", static_cast<double>(instrumentedJacobian.GetStatistics().applications)},
{"measured_jacobian_application_seconds", instrumentedJacobian.GetStatistics().totalSeconds},
{"measured_jacobian_maximum_application_seconds", instrumentedJacobian.GetStatistics().maximumSeconds},
{"measured_inverse_preconditioner_applications",
static_cast<double>(instrumentedPreconditioner.GetStatistics().applications)},
{"measured_inverse_preconditioner_application_seconds",
instrumentedPreconditioner.GetStatistics().totalSeconds}}
);
std::vector<solver::RitzValueMeasurement> orderedRitzValues = spectrum.ritzValues;
std::ranges::sort(orderedRitzValues, [](const auto &left, const auto &right) {
if (left.realPart != right.realPart) {
return left.realPart < right.realPart;
}
return left.imaginaryPart < right.imaginaryPart;
});
for (std::size_t index = 0; index < orderedRitzValues.size(); ++index) {
const solver::RitzValueMeasurement &ritz = orderedRitzValues[index];
experiment::record_experiment_result(
"stellar_preconditioning_p0", "identity_ritz_" + std::to_string(index),
common_parameters("ritz_value", problem.StateSize()),
{{"ritz_index", static_cast<double>(index)},
{"ritz_real", ritz.realPart},
{"ritz_imaginary", ritz.imaginaryPart},
{"ritz_magnitude", ritz.magnitude},
{"ritz_distance_from_one", ritz.distanceFromOne},
{"ritz_residual_estimate", ritz.residualEstimate},
{"ritz_relative_residual_estimate", ritz.relativeResidualEstimate},
{"ritz_converged", ritz.converged ? 1.0 : 0.0}}
);
}
int rank{0};
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "P0 identity baseline: " << solveMeasurement.outerIterations << " FGMRES iterations, "
<< spectrum.achievedDimension << " Arnoldi vectors, true relative residual "
<< solveMeasurement.directResidual.relativeResidual << '\n';
}
}

View File

@@ -0,0 +1,529 @@
#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
struct DeterminantPolynomial final {
double linear{0.0};
double quadratic{0.0};
double cubic{0.0};
};
struct CriticalAmplitude final {
double magnitude{0.0};
double determinant{1.0};
bool searchLimitReached{false};
};
struct SymmetricFiniteDifferenceStep final {
double step{0.0};
double positiveMinimumDeterminant{0.0};
double negativeMinimumDeterminant{0.0};
};
struct PolynomialRoots final {
std::array<double, 3> values{
std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::quiet_NaN()
};
int count{0};
};
[[nodiscard]] double relative_difference(
const mfem::Vector &computed,
const mfem::Vector &reference,
const MPI_Comm communicator
) {
mfem::Vector difference(computed);
difference -= reference;
const double scale = std::max(
{experiment::null_space::global_norm(computed, communicator),
experiment::null_space::global_norm(reference, communicator), std::numeric_limits<double>::epsilon()}
);
return experiment::null_space::global_norm(difference, communicator) / scale;
}
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + experiment::null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
[[nodiscard]] std::vector<DeterminantPolynomial> collect_determinant_polynomials(
const mean_field::fem::FEM &fem,
const mfem::Vector &unitVolumeDirection
) {
MFEM_VERIFY(fem.mesh->SpaceDimension() == 3, "The spherical-harmonic frequency probe requires 3D geometry.");
mfem::ParGridFunction displacement(fem.displacementFes.get());
displacement.SetFromTrueDofs(unitVolumeDirection);
std::vector<DeterminantPolynomial> polynomials;
polynomials.reserve(static_cast<std::size_t>(fem.mesh->GetNE()) * 64);
for (int element = 0; element < fem.mesh->GetNE(); ++element) {
mfem::ElementTransformation *transformation = fem.mesh->GetElementTransformation(element);
const mfem::FiniteElement *finiteElement = fem.displacementFes->GetFE(element);
const int integrationOrder =
std::max(finiteElement->GetOrder() + 2, 2 * fem.mesh->SpaceDimension() * finiteElement->GetOrder());
const mfem::IntegrationRule &rule = mfem::IntRules.Get(transformation->GetGeometryType(), integrationOrder);
for (int point = 0; point < rule.GetNPoints(); ++point) {
transformation->SetIntPoint(&rule.IntPoint(point));
mfem::DenseMatrix gradient;
displacement.GetVectorGradient(*transformation, gradient);
double trace = 0.0;
double traceSquared = 0.0;
for (int row = 0; row < 3; ++row) {
trace += gradient(row, row);
for (int column = 0; column < 3; ++column) {
traceSquared += gradient(row, column) * gradient(column, row);
}
}
polynomials.push_back(
{.linear = trace, .quadratic = 0.5 * (trace * trace - traceSquared), .cubic = gradient.Det()}
);
}
}
return polynomials;
}
[[nodiscard]] double global_minimum_determinant(
const std::vector<DeterminantPolynomial> &polynomials,
const double amplitude,
const MPI_Comm communicator
) {
double localMinimum = std::numeric_limits<double>::infinity();
for (const DeterminantPolynomial &polynomial : polynomials) {
const double determinant =
1.0 +
amplitude * (polynomial.linear + amplitude * (polynomial.quadratic + amplitude * polynomial.cubic));
localMinimum = std::min(localMinimum, determinant);
}
double globalMinimum = std::numeric_limits<double>::infinity();
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
return globalMinimum;
}
[[nodiscard]] double evaluate(
const DeterminantPolynomial &polynomial,
const double amplitude
) {
return 1.0 +
amplitude * (polynomial.linear + amplitude * (polynomial.quadratic + amplitude * polynomial.cubic));
}
void append_root(
PolynomialRoots &roots,
const double root
) {
if (roots.count < static_cast<int>(roots.values.size()) && std::isfinite(root)) {
roots.values[static_cast<std::size_t>(roots.count++)] = root;
}
}
[[nodiscard]] PolynomialRoots real_roots(const DeterminantPolynomial &polynomial) {
PolynomialRoots roots;
const double coefficientScale =
std::max({1.0, std::abs(polynomial.linear), std::abs(polynomial.quadratic), std::abs(polynomial.cubic)});
const double tolerance = 64.0 * std::numeric_limits<double>::epsilon() * coefficientScale;
if (std::abs(polynomial.cubic) <= tolerance) {
if (std::abs(polynomial.quadratic) <= tolerance) {
if (std::abs(polynomial.linear) > tolerance) {
append_root(roots, -1.0 / polynomial.linear);
}
return roots;
}
const double discriminant = polynomial.linear * polynomial.linear - 4.0 * polynomial.quadratic;
const double discriminantTolerance =
64.0 * std::numeric_limits<double>::epsilon() * std::max(1.0, polynomial.linear * polynomial.linear);
if (discriminant < -discriminantTolerance) {
return roots;
}
const double squareRoot = std::sqrt(std::max(0.0, discriminant));
const double stableNumerator = -0.5 * (polynomial.linear + std::copysign(squareRoot, polynomial.linear));
if (stableNumerator == 0.0) {
append_root(roots, -polynomial.linear / (2.0 * polynomial.quadratic));
} else {
append_root(roots, stableNumerator / polynomial.quadratic);
if (squareRoot > std::sqrt(discriminantTolerance)) {
append_root(roots, 1.0 / stableNumerator);
}
}
return roots;
}
const double quadratic = polynomial.quadratic / polynomial.cubic;
const double linear = polynomial.linear / polynomial.cubic;
const double constant = 1.0 / polynomial.cubic;
const double depressedLinear = linear - quadratic * quadratic / 3.0;
const double depressedConstant =
2.0 * quadratic * quadratic * quadratic / 27.0 - quadratic * linear / 3.0 + constant;
const double halfConstant = 0.5 * depressedConstant;
const double thirdLinear = depressedLinear / 3.0;
const double discriminant = halfConstant * halfConstant + thirdLinear * thirdLinear * thirdLinear;
const double discriminantTolerance =
128.0 * std::numeric_limits<double>::epsilon() *
std::max({1.0, std::abs(halfConstant * halfConstant), std::abs(thirdLinear * thirdLinear * thirdLinear)});
const double shift = quadratic / 3.0;
if (discriminant > discriminantTolerance) {
const double squareRoot = std::sqrt(discriminant);
append_root(roots, std::cbrt(-halfConstant + squareRoot) + std::cbrt(-halfConstant - squareRoot) - shift);
} else if (std::abs(depressedLinear) <= tolerance || thirdLinear >= 0.0) {
append_root(roots, std::cbrt(-depressedConstant) - shift);
} else {
const double radius = 2.0 * std::sqrt(std::max(0.0, -thirdLinear));
const double cosineArgument = std::clamp(
-halfConstant / std::sqrt(std::max(0.0, -thirdLinear * thirdLinear * thirdLinear)), -1.0, 1.0
);
const double phase = std::acos(cosineArgument) / 3.0;
constexpr double twoPiOverThree = 2.0943951023931954923;
for (int root = 0; root < 3; ++root) {
append_root(roots, radius * std::cos(phase - twoPiOverThree * static_cast<double>(root)) - shift);
}
}
for (int root = 0; root < roots.count; ++root) {
double &value = roots.values[static_cast<std::size_t>(root)];
for (int iteration = 0; iteration < 3; ++iteration) {
const double derivative =
polynomial.linear + value * (2.0 * polynomial.quadratic + 3.0 * value * polynomial.cubic);
if (std::abs(derivative) <= tolerance) {
break;
}
value -= evaluate(polynomial, value) / derivative;
}
}
return roots;
}
[[nodiscard]] CriticalAmplitude find_critical_amplitude(
const std::vector<DeterminantPolynomial> &polynomials,
const double sign,
const MPI_Comm communicator
) {
constexpr double maximumSearchMagnitude = 0.5;
MFEM_VERIFY(sign == 1.0 || sign == -1.0, "The critical-amplitude direction must be positive or negative.");
double localCriticalMagnitude = std::numeric_limits<double>::infinity();
for (const DeterminantPolynomial &polynomial : polynomials) {
const PolynomialRoots roots = real_roots(polynomial);
for (int root = 0; root < roots.count; ++root) {
const double signedMagnitude = sign * roots.values[static_cast<std::size_t>(root)];
if (signedMagnitude > 0.0) {
localCriticalMagnitude = std::min(localCriticalMagnitude, signedMagnitude);
}
}
}
double globalCriticalMagnitude = std::numeric_limits<double>::infinity();
MPI_Allreduce(&localCriticalMagnitude, &globalCriticalMagnitude, 1, MPI_DOUBLE, MPI_MIN, communicator);
if (!std::isfinite(globalCriticalMagnitude) || globalCriticalMagnitude > maximumSearchMagnitude) {
return {
.magnitude = maximumSearchMagnitude,
.determinant = global_minimum_determinant(polynomials, sign * maximumSearchMagnitude, communicator),
.searchLimitReached = true
};
}
return {
.magnitude = globalCriticalMagnitude,
.determinant = global_minimum_determinant(polynomials, sign * globalCriticalMagnitude, communicator),
.searchLimitReached = false
};
}
[[nodiscard]] SymmetricFiniteDifferenceStep find_symmetric_finite_difference_step(
const mean_field::deformation::PreparedDomainDeformationRuntime &deformation,
const mfem::Vector &unitVolumeDirection
) {
constexpr double requestedStep = 1.0e-4;
constexpr double minimumStep = 1.0e-10;
mfem::Vector trialVolumeDirection(unitVolumeDirection.Size());
for (double step = requestedStep; step >= minimumStep; step *= 0.25) {
trialVolumeDirection = unitVolumeDirection;
trialVolumeDirection *= step;
const mean_field::deformation::DomainDeformationGeometryReport positive =
deformation.inspectMappedGeometry(trialVolumeDirection);
trialVolumeDirection *= -1.0;
const mean_field::deformation::DomainDeformationGeometryReport negative =
deformation.inspectMappedGeometry(trialVolumeDirection);
if (positive.isOrientationPreserving() && negative.isOrientationPreserving()) {
return {
.step = step,
.positiveMinimumDeterminant = positive.minimumJacobianDeterminant,
.negativeMinimumDeterminant = negative.minimumJacobianDeterminant
};
}
}
throw std::domain_error(
"No symmetric orientation-preserving finite-difference step was found for the surface mode."
);
}
} // namespace
TEST_CASE(
"Reduced Surface Mode Reachability And Stellar Equilibrium Linearization",
"[null_space][surface_modes][reachability][linearization]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = 1.0e-12;
args.p.atol = std::min(args.p.atol, 1.0e-14);
args.p.max_iters = std::max(args.p.max_iters, 2000);
experiment::null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const auto modes = experiment::null_space::make_surface_modes(fixture);
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
const int totalCases = static_cast<int>(rotationFractions.size() * modes.size());
int completedCases = 0;
for (const double rotationFraction : rotationFractions) {
const mean_field::physics::RigidRotation rotation = fixture.rotation(rotationFraction);
fixture.prepare(fixture.state(), rotation);
const mfem::Vector baseResidual = fixture.residual();
REQUIRE(std::isfinite(experiment::null_space::global_norm(baseResidual, communicator)));
for (const experiment::null_space::SurfaceMode &mode : modes) {
experiment::null_space::report_progress(
communicator, "probing " + mode.name + " at rotation fraction " + std::to_string(rotationFraction) +
" (" + std::to_string(completedCases + 1) + "/" + std::to_string(totalCases) + ")"
);
fixture.prepare(fixture.state(), rotation);
const mfem::Vector action = fixture.jacobian_action(mode.direction);
const mfem::Vector liftedDirection = fixture.lifted_surface_direction(mode.direction);
const double inputNorm = experiment::null_space::global_norm(mode.direction, communicator);
const double actionNorm = experiment::null_space::global_norm(action, communicator);
const double liftNorm = experiment::null_space::global_norm(liftedDirection, communicator);
const SymmetricFiniteDifferenceStep coarseStep = find_symmetric_finite_difference_step(
fixture.stellar_operator().GetDomainDeformation(), liftedDirection
);
const std::array<double, 2> finiteDifferenceSteps{coarseStep.step, 1.0e-2 * coarseStep.step};
REQUIRE(inputNorm > 0.0);
REQUIRE(liftNorm > 0.0);
REQUIRE(std::isfinite(actionNorm));
std::map<std::string, double> metrics{
{"surface_parameter_input_norm", inputNorm},
{"lifted_volume_displacement_norm", liftNorm},
{"lift_amplification", liftNorm / inputNorm},
{"root_jacobian_action_norm", actionNorm},
{"root_action_per_surface_parameter_norm", actionNorm / inputNorm},
{"root_action_per_lifted_volume_norm", actionNorm / liftNorm},
{"base_residual_norm", experiment::null_space::global_norm(baseResidual, communicator)},
{"surface_parameter_count",
static_cast<double>(fixture.stellar_operator().GetDomainDeformation().parameterCount())},
{"volume_displacement_count",
static_cast<double>(fixture.stellar_operator().GetDomainDeformation().volumeDisplacementSize())},
{"finite_difference_coarse_step", finiteDifferenceSteps[0]},
{"finite_difference_fine_step", finiteDifferenceSteps[1]},
{"coarse_step_positive_minimum_determinant", coarseStep.positiveMinimumDeterminant},
{"coarse_step_negative_minimum_determinant", coarseStep.negativeMinimumDeterminant}
};
add_block_metrics(
metrics, "root_",
experiment::null_space::residual_block_norms(
action, fixture.stellar_operator().GetLayout(), communicator
)
);
for (const double step : finiteDifferenceSteps) {
mfem::Vector plusState(fixture.state());
plusState.Add(step, mode.direction);
fixture.prepare(plusState, rotation);
const mfem::Vector plusResidual = fixture.residual();
mfem::Vector minusState(fixture.state());
minusState.Add(-step, mode.direction);
fixture.prepare(minusState, rotation);
const mfem::Vector minusResidual = fixture.residual();
mfem::Vector finiteDifference(plusResidual);
finiteDifference -= minusResidual;
finiteDifference /= 2.0 * step;
const std::string stepName = step == finiteDifferenceSteps.front() ? "coarse" : "fine";
metrics.emplace(
"finite_difference_relative_error_" + stepName,
relative_difference(action, finiteDifference, communicator)
);
}
fixture.prepare(fixture.state(), rotation);
if (rank == 0) {
experiment::record_experiment_result(
"reduced_surface_mode_reachability", mode.name,
{{"mode_kind", experiment::null_space::surface_mode_kind_name(mode.kind)},
{"axis", std::to_string(mode.axis)},
{"rotation_fraction_of_keplerian", std::to_string(rotationFraction)},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
std::move(metrics)
);
}
++completedCases;
experiment::null_space::report_progress(
communicator, "completed " + std::to_string(completedCases) + "/" + std::to_string(totalCases) +
" reduced surface-mode cases"
);
}
}
experiment::null_space::report_progress(communicator, "reduced surface-mode probe complete; writing CSV output");
}
TEST_CASE(
"Spherical Harmonic Surface Frequencies Preserve Orientation Up To Measured Critical Amplitudes",
"[surface_modes][frequency_limit][geometry][spherical_harmonic]"
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(fem.okay());
experiment::null_space::Model model = experiment::null_space::make_model();
auto deformation = model.compileDomainDeformation(fem);
const auto &surface = deformation.surfaceDeformationPrescription();
const MPI_Comm communicator = fem.mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
constexpr std::array<int, 13> angularDegrees{0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20};
mfem::Vector zeroParameters(surface.parameterCount());
zeroParameters = 0.0;
for (std::size_t degreeIndex = 0; degreeIndex < angularDegrees.size(); ++degreeIndex) {
const int angularDegree = angularDegrees[degreeIndex];
experiment::null_space::report_progress(
communicator, "measuring zonal spherical-harmonic degree " + std::to_string(angularDegree) + " (" +
std::to_string(degreeIndex + 1) + "/" + std::to_string(angularDegrees.size()) + ")"
);
mfem::Vector parameters(surface.parameterCount());
double localMaximumAngularMagnitude = 0.0;
for (int parameter = 0; parameter < parameters.Size(); ++parameter) {
const double angularValue =
experiment::null_space::zonal_legendre(angularDegree, surface.radialDirection(parameter, 2));
parameters(parameter) = surface.referenceRadius(parameter) * angularValue;
localMaximumAngularMagnitude = std::max(localMaximumAngularMagnitude, std::abs(angularValue));
}
double globalMaximumAngularMagnitude = 0.0;
MPI_Allreduce(
&localMaximumAngularMagnitude, &globalMaximumAngularMagnitude, 1, MPI_DOUBLE, MPI_MAX, communicator
);
REQUIRE(globalMaximumAngularMagnitude > 0.0);
parameters /= globalMaximumAngularMagnitude;
mfem::Vector unitVolumeDirection(deformation.volumeDisplacementSize());
deformation.applyJacobian(zeroParameters, parameters, unitVolumeDirection);
const std::vector<DeterminantPolynomial> determinantPolynomials =
collect_determinant_polynomials(fem, unitVolumeDirection);
long long localSampleCount = static_cast<long long>(determinantPolynomials.size());
long long globalSampleCount = 0;
MPI_Allreduce(&localSampleCount, &globalSampleCount, 1, MPI_LONG_LONG, MPI_SUM, communicator);
REQUIRE(globalSampleCount > 0);
const CriticalAmplitude positiveCritical = find_critical_amplitude(determinantPolynomials, 1.0, communicator);
const CriticalAmplitude negativeCritical = find_critical_amplitude(determinantPolynomials, -1.0, communicator);
const double determinantPositive1e4 = global_minimum_determinant(determinantPolynomials, 1.0e-4, communicator);
const double determinantNegative1e4 = global_minimum_determinant(determinantPolynomials, -1.0e-4, communicator);
const double determinantPositive1e3 = global_minimum_determinant(determinantPolynomials, 1.0e-3, communicator);
const double determinantNegative1e3 = global_minimum_determinant(determinantPolynomials, -1.0e-3, communicator);
const double determinantPositive1e2 = global_minimum_determinant(determinantPolynomials, 1.0e-2, communicator);
const double determinantNegative1e2 = global_minimum_determinant(determinantPolynomials, -1.0e-2, communicator);
if (angularDegree == 12) {
mfem::Vector directInspectionDirection(unitVolumeDirection);
directInspectionDirection *= 1.0e-3;
const mean_field::deformation::DomainDeformationGeometryReport directInspection =
deformation.inspectMappedGeometry(directInspectionDirection);
const double comparisonScale = std::max(
{1.0, std::abs(directInspection.minimumJacobianDeterminant), std::abs(determinantPositive1e3)}
);
CHECK(
std::abs(directInspection.minimumJacobianDeterminant - determinantPositive1e3) <=
1.0e-11 * comparisonScale
);
}
REQUIRE(std::isfinite(positiveCritical.magnitude));
REQUIRE(std::isfinite(negativeCritical.magnitude));
REQUIRE(positiveCritical.magnitude > 0.0);
REQUIRE(negativeCritical.magnitude > 0.0);
if (rank == 0) {
experiment::record_experiment_result(
"spherical_harmonic_surface_frequency_limit", "zonal_l" + std::to_string(angularDegree),
{{"angular_degree", std::to_string(angularDegree)},
{"azimuthal_order", "0"},
{"positive_limit_censored", positiveCritical.searchLimitReached ? "true" : "false"},
{"negative_limit_censored", negativeCritical.searchLimitReached ? "true" : "false"},
{"mesh_file", test_utils::setup_args().mesh_file}},
{{"positive_critical_fractional_amplitude", positiveCritical.magnitude},
{"negative_critical_fractional_amplitude", negativeCritical.magnitude},
{"positive_critical_determinant", positiveCritical.determinant},
{"negative_critical_determinant", negativeCritical.determinant},
{"minimum_determinant_positive_1e-4", determinantPositive1e4},
{"minimum_determinant_negative_1e-4", determinantNegative1e4},
{"minimum_determinant_positive_1e-3", determinantPositive1e3},
{"minimum_determinant_negative_1e-3", determinantNegative1e3},
{"minimum_determinant_positive_1e-2", determinantPositive1e2},
{"minimum_determinant_negative_1e-2", determinantNegative1e2},
{"surface_parameter_norm", experiment::null_space::global_norm(parameters, communicator)},
{"lifted_volume_displacement_norm",
experiment::null_space::global_norm(unitVolumeDirection, communicator)},
{"global_geometry_sample_count", static_cast<double>(globalSampleCount)}}
);
}
}
experiment::null_space::report_progress(
communicator, "spherical-harmonic frequency-limit probe complete; writing CSV output"
);
}

View File

@@ -0,0 +1,531 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
export module experiment.stellar_null_space;
import mean_field;
import test_helpers;
export namespace experiment::null_space {
using Form = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using Model = mean_field::models::StellarModel<mean_field::models::structure::PolytropicStructure>;
constexpr auto densityValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto surfaceDeformationValue = mean_field::utils::blocks::get_value_block<Form>(
mean_field::utils::blocks::surface_deformation_field.parameters_term
);
constexpr auto gravityGradientValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto bernoulliValue = mean_field::utils::blocks::get_value_block<Form>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
constexpr auto gravityGradientResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto surfaceShapeResidual = mean_field::utils::blocks::get_residual_block<Form>(
mean_field::utils::blocks::surface_deformation_field.shape_equilibrium_term
);
constexpr auto enthalpyResidual =
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<Form>(
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
);
inline constexpr std::array<const char *, 6> residualBlockNames{"gravity_gradient", "gravity_potential", "closure",
"surface_shape", "hydrostatic", "mass"};
template <int index>
[[nodiscard]] mfem::Vector value_view(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block
) {
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector const_value_view(
const mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block
) {
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector residual_view(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
}
template <int index>
[[nodiscard]] mfem::Vector const_residual_view(
const mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::residual_block<index> block
) {
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
}
template <int index>
void assign_value_block(
mfem::Vector &vector,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mean_field::utils::blocks::value_block<index> block,
const mfem::Vector &source
) {
MFEM_VERIFY(
source.Size() == layout.size(block), "Surface-mode experiment received a block with the wrong size."
);
value_view(vector, layout, block) = source;
}
[[nodiscard]] inline double global_norm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
const double localNormSquared = vector * vector;
double globalNormSquared = 0.0;
MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, communicator);
return std::sqrt(globalNormSquared);
}
inline void report_progress(
const MPI_Comm communicator,
const std::string &message
) {
int rank = 0;
MPI_Comm_rank(communicator, &rank);
if (rank == 0) {
std::cout << "[reduced-surface experiment] " << message << std::endl;
}
}
[[nodiscard]] inline mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
return {
.discretization = {.identity = 2003, .revision = 1},
.density = {.identity = 2011, .revision = 1},
.surfaceDeformation = {.identity = 2017, .revision = 1},
.gravityGradient = {.identity = 2027, .revision = 1},
.gravityPotential = {.identity = 2029, .revision = 1},
.enthalpy = {.identity = 2039, .revision = 1},
.bernoulliConstant = {.identity = 2053, .revision = 1},
.rotation = {.identity = 2063, .revision = 1},
.targetMass = {.identity = 2069, .revision = 1}
};
}
inline void increment_state_revisions(mean_field::operators::StellarEquilibriumDependencies &dependencies) {
++dependencies.density.revision;
++dependencies.surfaceDeformation.revision;
++dependencies.gravityGradient.revision;
++dependencies.gravityPotential.revision;
++dependencies.enthalpy.revision;
++dependencies.bernoulliConstant.revision;
}
[[nodiscard]] inline mfem::Vector pack_gravity_state(
const mfem::Vector &density,
const mfem::Vector &displacement,
const mfem::Vector &gravityGradient,
const mfem::Vector &gravityPotential
) {
const std::array<int, 5> offsets{
0, density.Size(), density.Size() + displacement.Size(),
density.Size() + displacement.Size() + gravityGradient.Size(),
density.Size() + displacement.Size() + gravityGradient.Size() + gravityPotential.Size()
};
mfem::Vector packed(offsets.back());
mfem::Vector(packed.GetData() + offsets[0], density.Size()) = density;
mfem::Vector(packed.GetData() + offsets[1], displacement.Size()) = displacement;
mfem::Vector(packed.GetData() + offsets[2], gravityGradient.Size()) = gravityGradient;
mfem::Vector(packed.GetData() + offsets[3], gravityPotential.Size()) = gravityPotential;
return packed;
}
[[nodiscard]] inline Model make_model() {
const double pi = std::acos(-1.0);
const double targetMass = mean_field::utils::MASS;
constexpr double dimensionlessMass = 2.0182359509662283534;
const double polytropicConstant =
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
return Model{
mean_field::models::structure::PolytropicStructure{
mean_field::eos::Polytrope{3.0, polytropicConstant}, targetMass
},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
};
}
class N3Equilibrium final {
public:
explicit N3Equilibrium(mean_field::utils::Args args)
: m_args(std::move(args)),
m_fem(
mean_field::fem::setup_fem(
m_args.mesh_file,
m_args,
0
)
),
m_model(make_model()),
m_operator(
m_fem,
*m_fem.domainMapperStateless,
m_model
),
m_state(m_operator.GetLayout().value_offsets().Last()),
m_dependencies(make_dependencies()) {
MFEM_VERIFY(m_fem.okay(), "The null-space experiment could not construct the finite-element problem.");
m_state = 0.0;
initialize_state();
}
[[nodiscard]] mean_field::fem::FEM &fem() noexcept {
return m_fem;
}
[[nodiscard]] const mean_field::fem::FEM &fem() const noexcept {
return m_fem;
}
[[nodiscard]] Model &model() noexcept {
return m_model;
}
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumOperator &stellar_operator() noexcept {
return m_operator;
}
[[nodiscard]] const mean_field::operators::PreparedStellarEquilibriumOperator &
stellar_operator() const noexcept {
return m_operator;
}
[[nodiscard]] const mfem::Vector &state() const noexcept {
return m_state;
}
[[nodiscard]] mean_field::physics::RigidRotation rotation(const double fractionOfKeplerian) const {
const double radius = mean_field::utils::RADIUS;
const double mass = mean_field::utils::MASS;
const double keplerianSpeed = std::sqrt(mean_field::utils::G * mass / (radius * radius * radius));
mfem::Vector angularVelocity(3);
angularVelocity = 0.0;
angularVelocity(2) = fractionOfKeplerian * keplerianSpeed;
mfem::Vector center(3);
center = 0.0;
return mean_field::physics::RigidRotation(angularVelocity, center);
}
void prepare(
const mfem::Vector &state,
const mean_field::physics::RigidRotation &rotation
) {
m_currentState = state;
increment_state_revisions(m_dependencies);
++m_dependencies.rotation.revision;
m_operator.Prepare(state, m_dependencies, rotation);
}
[[nodiscard]] mfem::Vector residual() const {
mfem::Vector result;
m_operator.BuildResidual(result);
return result;
}
[[nodiscard]] mfem::Vector jacobian_action(const mfem::Vector &direction) const {
mfem::Vector result;
m_operator.Mult(direction, result);
return result;
}
[[nodiscard]] mfem::Vector lifted_surface_direction(const mfem::Vector &rootDirection) const {
const auto &layout = m_operator.GetLayout();
const mfem::Vector surfaceDirection = const_value_view(rootDirection, layout, surfaceDeformationValue);
mfem::Vector volumeDirection(m_operator.GetDomainDeformation().volumeDisplacementSize());
m_operator.GetDomainDeformation().applyJacobian(
m_operator.GetSurfaceDeformationParameters(), surfaceDirection, volumeDirection
);
return volumeDirection;
}
private:
void initialize_state() {
report_progress(m_fem.mesh->GetComm(), "constructing the analytic n=3 Lane-Emden state");
constexpr double surfaceCoordinate = 6.8968486193769603755;
constexpr int radialSampleCount = 8192;
const double pi = std::acos(-1.0);
const double radius = mean_field::utils::RADIUS;
const double targetMass = mean_field::utils::MASS;
constexpr double dimensionlessMass = 2.0182359509662283534;
const double polytropicConstant =
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
const double centralDensity =
std::pow(surfaceCoordinate * std::sqrt(polytropicConstant / (pi * mean_field::utils::G)) / radius, 3.0);
const mean_field::models::structure::StructureSeed seed =
m_model.makeInitialSeed({.centralDensity = centralDensity, .radialSampleCount = radialSampleCount});
const auto interpolate = [](const mfem::Vector &radii, const mfem::Vector &values, const double r) {
if (r <= radii(0)) {
return values(0);
}
const int finalIndex = radii.Size() - 1;
if (r >= radii(finalIndex)) {
return values(finalIndex);
}
int lower = 0;
int upper = finalIndex;
while (upper - lower > 1) {
const int middle = lower + (upper - lower) / 2;
if (radii(middle) <= r) {
lower = middle;
} else {
upper = middle;
}
}
const double fraction = (r - radii(lower)) / (radii(upper) - radii(lower));
return (1.0 - fraction) * values(lower) + fraction * values(upper);
};
mfem::FunctionCoefficient densityCoefficient([&seed, &interpolate](const mfem::Vector &position) {
const double r = position.Norml2();
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.density, r);
});
mfem::FunctionCoefficient enthalpyCoefficient([&seed, &interpolate](const mfem::Vector &position) {
const double r = position.Norml2();
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.enthalpy, r);
});
mfem::ParGridFunction densityField(m_fem.densityFes.get());
mfem::ParGridFunction enthalpyField(m_fem.enthalpyFes.get());
mfem::ParGridFunction displacementField(m_fem.displacementFes.get());
densityField = 0.0;
enthalpyField = 0.0;
displacementField = 0.0;
densityField.ProjectCoefficient(densityCoefficient);
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
*m_fem.displacement = displacementField;
report_progress(m_fem.mesh->GetComm(), "solving the gravity field for the seed state");
const mean_field::physics::GravitySolution gravity =
mean_field::physics::solve_gravity_field(m_fem, m_args, densityField, displacementField);
mfem::Vector densityTrue;
mfem::Vector enthalpyTrue;
mfem::Vector gravityGradientTrue;
mfem::Vector gravityPotentialTrue;
densityField.GetTrueDofs(densityTrue);
enthalpyField.GetTrueDofs(enthalpyTrue);
gravity.gradPhi.GetTrueDofs(gravityGradientTrue);
gravity.phi.GetTrueDofs(gravityPotentialTrue);
const auto &layout = m_operator.GetLayout();
const mean_field::field::FieldDofMap densityMap =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*m_fem.densityFes);
const mean_field::field::FieldDofMap enthalpyMap =
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*m_fem.enthalpyFes);
mfem::Vector surfaceParameters(layout.size(surfaceDeformationValue));
surfaceParameters = 0.0;
assign_value_block(m_state, layout, densityValue, densityMap.gather(densityTrue));
assign_value_block(m_state, layout, surfaceDeformationValue, surfaceParameters);
assign_value_block(m_state, layout, gravityGradientValue, gravityGradientTrue);
assign_value_block(m_state, layout, gravityPotentialValue, gravityPotentialTrue);
assign_value_block(m_state, layout, enthalpyValue, enthalpyMap.gather(enthalpyTrue));
value_view(m_state, layout, bernoulliValue)(0) = -mean_field::utils::G * targetMass / radius;
m_currentState = m_state;
prepare(m_state, rotation(0.0));
report_progress(m_fem.mesh->GetComm(), "analytic state is prepared");
}
mean_field::utils::Args m_args;
mean_field::fem::FEM m_fem;
Model m_model;
mean_field::operators::PreparedStellarEquilibriumOperator m_operator;
mfem::Vector m_state;
mfem::Vector m_currentState;
mean_field::operators::StellarEquilibriumDependencies m_dependencies;
};
enum class SurfaceModeKind : std::uint8_t {
uniform_radial,
translation_like_dipole,
oblate_quadrupole,
spherical_harmonic
};
struct SurfaceMode final {
std::string name;
SurfaceModeKind kind;
int axis;
mfem::Vector direction;
};
[[nodiscard]] inline const char *surface_mode_kind_name(const SurfaceModeKind kind) noexcept {
switch (kind) {
case SurfaceModeKind::uniform_radial:
return "uniform_radial";
case SurfaceModeKind::translation_like_dipole:
return "translation_like_dipole";
case SurfaceModeKind::oblate_quadrupole:
return "oblate_quadrupole";
case SurfaceModeKind::spherical_harmonic:
return "spherical_harmonic";
}
return "unknown";
}
[[nodiscard]] inline double zonal_legendre(
const int degree,
const double cosineOfPolarAngle
) {
MFEM_VERIFY(degree >= 0, "A zonal spherical-harmonic degree must be non-negative.");
const double coordinate = std::clamp(cosineOfPolarAngle, -1.0, 1.0);
if (degree == 0) {
return 1.0;
}
if (degree == 1) {
return coordinate;
}
double previousPrevious = 1.0;
double previous = coordinate;
for (int order = 2; order <= degree; ++order) {
const double current = ((2.0 * static_cast<double>(order) - 1.0) * coordinate * previous -
(static_cast<double>(order) - 1.0) * previousPrevious) /
static_cast<double>(order);
previousPrevious = previous;
previous = current;
}
return previous;
}
[[nodiscard]] inline std::vector<SurfaceMode> make_surface_modes(N3Equilibrium &fixture) {
const auto &layout = fixture.stellar_operator().GetLayout();
auto deformation = fixture.model().compileDomainDeformation(fixture.fem());
const auto &surface = deformation.surfaceDeformationPrescription();
MFEM_VERIFY(
surface.parameterCount() == layout.size(surfaceDeformationValue),
"The diagnostic surface prescription does not match the root surface block."
);
const auto make_root_direction = [&layout](const mfem::Vector &surfaceDirection) {
mfem::Vector direction(layout.value_offsets().Last());
direction = 0.0;
assign_value_block(direction, layout, surfaceDeformationValue, surfaceDirection);
return direction;
};
std::vector<SurfaceMode> modes;
modes.reserve(6);
mfem::Vector uniform(surface.parameterCount());
for (int parameter = 0; parameter < uniform.Size(); ++parameter) {
uniform(parameter) = surface.referenceRadius(parameter);
}
modes.push_back(
{.name = "uniform_radial_homology",
.kind = SurfaceModeKind::uniform_radial,
.axis = -1,
.direction = make_root_direction(uniform)}
);
for (int axis = 0; axis < surface.spatialDimension(); ++axis) {
mfem::Vector dipole(surface.parameterCount());
for (int parameter = 0; parameter < dipole.Size(); ++parameter) {
dipole(parameter) = surface.radialDirection(parameter, axis);
}
modes.push_back(
{.name = std::string("translation_like_dipole_") + static_cast<char>('x' + axis),
.kind = SurfaceModeKind::translation_like_dipole,
.axis = axis,
.direction = make_root_direction(dipole)}
);
}
mfem::Vector quadrupole(surface.parameterCount());
for (int parameter = 0; parameter < quadrupole.Size(); ++parameter) {
const double polarDirection = surface.radialDirection(parameter, 2);
quadrupole(parameter) = surface.referenceRadius(parameter) * (1.0 - 3.0 * polarDirection * polarDirection);
}
modes.push_back(
{.name = "axisymmetric_oblate_quadrupole_z",
.kind = SurfaceModeKind::oblate_quadrupole,
.axis = 2,
.direction = make_root_direction(quadrupole)}
);
constexpr int diagnosticAngularDegree = 12;
mfem::Vector sphericalHarmonic(surface.parameterCount());
double localMaximumMagnitude = 0.0;
for (int parameter = 0; parameter < sphericalHarmonic.Size(); ++parameter) {
const double angularValue = zonal_legendre(diagnosticAngularDegree, surface.radialDirection(parameter, 2));
sphericalHarmonic(parameter) = surface.referenceRadius(parameter) * angularValue;
localMaximumMagnitude = std::max(localMaximumMagnitude, std::abs(angularValue));
}
double globalMaximumMagnitude = 0.0;
MPI_Allreduce(
&localMaximumMagnitude, &globalMaximumMagnitude, 1, MPI_DOUBLE, MPI_MAX, fixture.fem().mesh->GetComm()
);
MFEM_VERIFY(globalMaximumMagnitude > 0.0, "The spherical-harmonic surface mode has zero amplitude.");
sphericalHarmonic /= globalMaximumMagnitude;
modes.push_back(
{.name = "zonal_spherical_harmonic_l12",
.kind = SurfaceModeKind::spherical_harmonic,
.axis = -1,
.direction = make_root_direction(sphericalHarmonic)}
);
return modes;
}
[[nodiscard]] inline std::array<
double,
6>
residual_block_norms(
const mfem::Vector &action,
const mean_field::operators::StellarEquilibriumLayout &layout,
const MPI_Comm communicator
) {
return {
global_norm(const_residual_view(action, layout, gravityGradientResidual), communicator),
global_norm(const_residual_view(action, layout, gravityPotentialResidual), communicator),
global_norm(const_residual_view(action, layout, densityResidual), communicator),
global_norm(const_residual_view(action, layout, surfaceShapeResidual), communicator),
global_norm(const_residual_view(action, layout, enthalpyResidual), communicator),
global_norm(const_residual_view(action, layout, massResidual), communicator)
};
}
} // namespace experiment::null_space

View File

@@ -0,0 +1,50 @@
add_library(mean_field_extension_example)
target_sources(
mean_field_extension_example
PUBLIC
FILE_SET CXX_MODULES FILES
ideal_gas_radiation.cppm
rotating_stellar_model.cppm
)
target_link_libraries(mean_field_extension_example PUBLIC mean_field)
add_executable(extension_example_demo demo.cpp)
target_link_libraries(extension_example_demo PRIVATE mean_field_extension_example)
add_executable(
extension_example_tests
tests/ideal_gas_radiation.cpp
tests/rotating_stellar_model.cpp
)
target_link_libraries(
extension_example_tests
PRIVATE
mean_field_extension_example
Catch2::Catch2WithMain
)
catch_discover_tests(
extension_example_tests
TEST_PREFIX "extension_example::"
PROPERTIES LABELS "extension-example"
)
find_program(LATEXMK_EXECUTABLE latexmk)
if (LATEXMK_EXECUTABLE)
add_custom_target(
extension_example_manual
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/manual"
COMMAND
${LATEXMK_EXECUTABLE}
-pdf
-interaction=nonstopmode
-halt-on-error
-outdir=${CMAKE_CURRENT_BINARY_DIR}/manual
"${CMAKE_CURRENT_SOURCE_DIR}/manual/physics_developer_manual.tex"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/manual"
COMMENT "Compiling the MeanField physics developer manual"
VERBATIM
)
endif ()

View File

@@ -0,0 +1,55 @@
# MeanField physics extension example
This directory is a small, isolated example for physicists who want to extend
MeanField without first learning its internal block-matrix machinery.
Start in this order:
1. Read `ideal_gas_radiation.cppm`. It implements a monatomic ideal gas plus
equilibrium radiation using the public EOS relation protocol.
2. Read `rotating_stellar_model.cppm`. It composes that EOS with the existing
isobaric surface, fixed-total-mass invariant, and fixed-angular-momentum
invariant.
3. Read and run `demo.cpp`.
4. Read the tests. They show which claims should be compile-time contracts and
which claims require physical or numerical checks.
5. Use `manual/physics_developer_manual.pdf` as the detailed guide. Its LaTeX
source is beside it.
## The important boundary
`makeRotatingStellarModel(...)` produces a valid, strongly typed stellar-model
specification. The current equilibrium numerical core is still barotropic: it
expects density to be closed by specific enthalpy alone. An ideal-gas plus
radiation EOS depends independently on density and temperature, so a complete
thermal equilibrium solve also needs a temperature or entropy field and its
governing equation.
The example therefore proves at compile time that model composition succeeds
and that the present discretizer rejects this model. It does not disguise the
thermal EOS as a polytrope or claim that a missing energy equation exists.
## Build only this example
From the repository root, configure as usual, then build only these targets:
```sh
cmake --build cmake-build-profile-homebrew-llvm \
--target extension_example_demo extension_example_tests
```
Run only the extension tests:
```sh
./cmake-build-profile-homebrew-llvm/extension_example/extension_example_tests
```
Compile a fresh manual into the build directory:
```sh
cmake --build cmake-build-profile-homebrew-llvm \
--target extension_example_manual
```
No source under `libmeanfield/` belongs to this example, and the extension test
executable is separate from the main MeanField regression suite.

View File

@@ -0,0 +1,43 @@
#include <iomanip>
#include <iostream>
import mean_field;
import mean_field_extension_example.rotating_stellar_model;
int main() {
using namespace mean_field;
using namespace mean_field::extension_example;
const IdealGasRadiation equationOfState({
.meanMolecularWeight = 0.61,
.boltzmannConstant = 1.380649e-16,
.atomicMassUnit = 1.66053906660e-24,
.radiationConstant = 7.5657e-15
});
const dimensions::DensityValue density{10.0}; // g cm^-3
const dimensions::TemperatureValue temperature{1.5e7}; // K
const auto pressure = eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
density,
temperature
);
const auto model = makeRotatingStellarModel({
.equationOfState = equationOfState.parameters(),
.surfacePressure = dimensions::PressureValue{0.0},
.totalMass = dimensions::MassValue{1.0},
.totalAngularMomentum = dimensions::AngularMomentumValue{0.2}
});
std::cout << std::scientific
<< "P(rho = 10 g cm^-3, T = 1.5e7 K) = "
<< pressure.value() << " dyn cm^-2\n"
<< "Compiled specification count = "
<< model.specificationCount << '\n'
<< "Current barotropic backend accepts this thermal model = "
<< std::boolalpha
<< currentEquilibriumBackendSupportsIdealGasRadiation << '\n';
return 0;
}

View File

@@ -0,0 +1,403 @@
module;
#include <cmath>
#include <stdexcept>
#include <string>
export module mean_field_extension_example.ideal_gas_radiation;
import mean_field;
/*
* This file is intended to be read from top to bottom by a physicist who is
* adding an equation of state (EOS). The comments explain the small amount
* of type-system vocabulary required by MeanField; the thermodynamics remain
* visible as ordinary equations.
*/
export namespace mean_field::extension_example {
namespace eos_quantity = mean_field::dimensions::quantity;
/*
* A relation is only a compile-time sentence:
*
* output = f(input 1, input 2, ...).
*
* Input order is significant. These declarations say that density is
* the first argument and temperature is the second argument. They do not
* allocate data and have no runtime cost.
*/
using PressureFromDensityAndTemperature = mean_field::eos::Relation<
eos_quantity::Pressure,
eos_quantity::Density,
eos_quantity::Temperature>;
using SpecificInternalEnergyFromDensityAndTemperature = mean_field::eos::Relation<
eos_quantity::SpecificInternalEnergy,
eos_quantity::Density,
eos_quantity::Temperature>;
using SpecificEnthalpyFromDensityAndTemperature = mean_field::eos::Relation<
eos_quantity::SpecificEnthalpy,
eos_quantity::Density,
eos_quantity::Temperature>;
/*
* A monatomic ideal gas plus equilibrium radiation:
*
* R = k_B / (mu m_u)
* P_gas = rho R T
* P_rad = a T^4 / 3
* u = (3/2) R T + a T^4 / rho
* h = u + P/rho
* = (5/2) R T + 4 a T^4 / (3 rho)
*
* The scalar QuantityValue wrappers identify what a number means. They
* intentionally do not perform unit conversion. Every number supplied
* here must therefore use one coherent unit system.
*/
class IdealGasRadiation final {
public:
struct Parameters final {
/* Mean particle mass in atomic-mass units. */
double meanMolecularWeight{0.61};
/* CGS defaults: erg K^-1, g, and erg cm^-3 K^-4. */
double boltzmannConstant{1.380649e-16};
double atomicMassUnit{1.66053906660e-24};
double radiationConstant{7.5657e-15};
};
/*
* This one alias makes the EOS a constitutive-law specification that
* can be placed directly in model::StellarModel(...). There is no
* registry edit and no central list of EOS combinations to maintain.
*/
using ModelDefinition = mean_field::eos::ConstitutiveLaw<IdealGasRadiation,"IdealGasRadiation">;
/*
* The catalog is the complete public claim made by this EOS. If an
* evaluate overload below is missing or has the wrong argument order,
* eos::EquationOfStateModel<IdealGasRadiation> becomes false at
* compile time.
*/
using Relations = mean_field::eos::RelationCatalog<
PressureFromDensityAndTemperature,
SpecificInternalEnergyFromDensityAndTemperature,
SpecificEnthalpyFromDensityAndTemperature
>;
struct PressureContributions final {
mean_field::dimensions::PressureValue gas;
mean_field::dimensions::PressureValue radiation;
[[nodiscard]] mean_field::dimensions::PressureValue total() const noexcept {
return gas + radiation;
}
};
explicit IdealGasRadiation(const Parameters parameters)
: m_parameters(validatedParameters(parameters)),
m_specificGasConstant(
m_parameters.boltzmannConstant /(m_parameters.meanMolecularWeight * m_parameters.atomicMassUnit)
) {}
[[nodiscard]] const Parameters &parameters() const noexcept {
return m_parameters;
}
[[nodiscard]] double specificGasConstant() const noexcept {
return m_specificGasConstant;
}
/*
* Named component functions are not required by the EOS protocol.
* They are provided because they make diagnostics and physics tests
* easier to read than repeated algebra in client code.
*/
[[nodiscard]] PressureContributions pressureContributions(
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
const double rho = density.value();
const double T = temperature.value();
return PressureContributions{
.gas = mean_field::dimensions::PressureValue{rho * m_specificGasConstant * T},
.radiation = mean_field::dimensions::PressureValue{
m_parameters.radiationConstant * fourthPower(T) / 3.0
}
};
}
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue gasSpecificInternalEnergy(
const mean_field::dimensions::TemperatureValue temperature
) const {
validateTemperature(temperature);
return mean_field::dimensions::SpecificInternalEnergyValue{
1.5 * m_specificGasConstant * temperature.value()
};
}
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue radiationSpecificInternalEnergy(
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::dimensions::SpecificInternalEnergyValue{
m_parameters.radiationConstant * fourthPower(temperature.value()) / density.value()
};
}
/* The evaluate overloads implement the three declared relations. */
[[nodiscard]] mean_field::dimensions::PressureValue evaluate(
PressureFromDensityAndTemperature,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
return pressureContributions(density, temperature).total();
}
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue evaluate(
SpecificInternalEnergyFromDensityAndTemperature,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
const auto gas = gasSpecificInternalEnergy(temperature);
const auto radiation = radiationSpecificInternalEnergy(density, temperature);
return gas + radiation;
}
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromDensityAndTemperature,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
const double rho = density.value();
const double T = temperature.value();
return mean_field::dimensions::SpecificEnthalpyValue{
2.5 * m_specificGasConstant * T +
4.0 * m_parameters.radiationConstant * fourthPower(T) / (3.0 * rho)
};
}
/*
* Jacobian entries are ordinary analytic partial derivatives. The
* WithRespectTo tag prevents accidentally returning dP/dT from the
* overload that promised dP/drho.
*/
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::Pressure,
eos_quantity::Density>
partialDerivative(
PressureFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Density>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::eos::PartialDerivative<
eos_quantity::Pressure,
eos_quantity::Density>{m_specificGasConstant * temperature.value()};
}
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::Pressure,
eos_quantity::Temperature>
partialDerivative(
PressureFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
const double T = temperature.value();
return mean_field::eos::PartialDerivative<
eos_quantity::Pressure,
eos_quantity::Temperature>{
density.value() * m_specificGasConstant +
4.0 * m_parameters.radiationConstant * cube(T) / 3.0
};
}
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::SpecificInternalEnergy,
eos_quantity::Density>
partialDerivative(
SpecificInternalEnergyFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Density>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::eos::PartialDerivative<
eos_quantity::SpecificInternalEnergy,
eos_quantity::Density>{
-m_parameters.radiationConstant * fourthPower(temperature.value()) /
square(density.value())
};
}
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::SpecificInternalEnergy,
eos_quantity::Temperature>
partialDerivative(
SpecificInternalEnergyFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::eos::PartialDerivative<
eos_quantity::SpecificInternalEnergy,
eos_quantity::Temperature>{
1.5 * m_specificGasConstant +
4.0 * m_parameters.radiationConstant * cube(temperature.value()) / density.value()
};
}
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::SpecificEnthalpy,
eos_quantity::Density>
partialDerivative(
SpecificEnthalpyFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Density>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::eos::PartialDerivative<
eos_quantity::SpecificEnthalpy,
eos_quantity::Density>{
-4.0 * m_parameters.radiationConstant * fourthPower(temperature.value()) /
(3.0 * square(density.value()))
};
}
[[nodiscard]] mean_field::eos::PartialDerivative<
eos_quantity::SpecificEnthalpy,
eos_quantity::Temperature>
partialDerivative(
SpecificEnthalpyFromDensityAndTemperature,
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) const {
validateMaterialState(density, temperature);
return mean_field::eos::PartialDerivative<
eos_quantity::SpecificEnthalpy,
eos_quantity::Temperature>{
2.5 * m_specificGasConstant +
16.0 * m_parameters.radiationConstant * cube(temperature.value()) /
(3.0 * density.value())
};
}
private:
[[nodiscard]] static Parameters validatedParameters(const Parameters parameters) {
requirePositiveFinite(parameters.meanMolecularWeight, "mean molecular weight");
requirePositiveFinite(parameters.boltzmannConstant, "Boltzmann constant");
requirePositiveFinite(parameters.atomicMassUnit, "atomic mass unit");
requireNonnegativeFinite(parameters.radiationConstant, "radiation constant");
return parameters;
}
static void requirePositiveFinite(const double value, const char *name) {
if (!std::isfinite(value) || value <= 0.0) {
throw std::invalid_argument(
std::string{"IdealGasRadiation requires a finite, positive "} + name + "."
);
}
}
static void requireNonnegativeFinite(const double value, const char *name) {
if (!std::isfinite(value) || value < 0.0) {
throw std::invalid_argument(
std::string{"IdealGasRadiation requires a finite, nonnegative "} + name + "."
);
}
}
static void validateMaterialState(
const mean_field::dimensions::DensityValue density,
const mean_field::dimensions::TemperatureValue temperature
) {
if (!std::isfinite(density.value()) || !std::isfinite(temperature.value())) {
throw mean_field::eos::EvaluationError{
mean_field::eos::EvaluationErrorCode::nonfinite_input,
"IdealGasRadiation requires finite density and temperature."
};
}
if (density.value() <= 0.0 || temperature.value() < 0.0) {
throw mean_field::eos::EvaluationError{
mean_field::eos::EvaluationErrorCode::outside_domain,
"IdealGasRadiation requires rho > 0 and T >= 0."
};
}
}
static void validateTemperature(const mean_field::dimensions::TemperatureValue temperature) {
if (!std::isfinite(temperature.value())) {
throw mean_field::eos::EvaluationError{
mean_field::eos::EvaluationErrorCode::nonfinite_input,
"IdealGasRadiation requires finite temperature."
};
}
if (temperature.value() < 0.0) {
throw mean_field::eos::EvaluationError{
mean_field::eos::EvaluationErrorCode::outside_domain,
"IdealGasRadiation requires T >= 0."
};
}
}
[[nodiscard]] static double square(const double value) noexcept {
return value * value;
}
[[nodiscard]] static double cube(const double value) noexcept {
return value * value * value;
}
[[nodiscard]] static double fourthPower(const double value) noexcept {
const double squared = square(value);
return squared * squared;
}
Parameters m_parameters;
double m_specificGasConstant;
};
/*
* These assertions are executable documentation. They prove that the
* class and every derivative satisfy the public extension protocol.
*/
static_assert(mean_field::models::SelfDescribingModelSpecification<IdealGasRadiation>);
static_assert(mean_field::eos::EquationOfStateModel<IdealGasRadiation>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
PressureFromDensityAndTemperature,
eos_quantity::Density>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
PressureFromDensityAndTemperature,
eos_quantity::Temperature>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
SpecificInternalEnergyFromDensityAndTemperature,
eos_quantity::Density>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
SpecificInternalEnergyFromDensityAndTemperature,
eos_quantity::Temperature>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
SpecificEnthalpyFromDensityAndTemperature,
eos_quantity::Density>);
static_assert(mean_field::eos::SupportsPartialDerivative<
IdealGasRadiation,
SpecificEnthalpyFromDensityAndTemperature,
eos_quantity::Temperature>);
} // namespace mean_field::extension_example

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
module;
#include <array>
#include <utility>
export module mean_field_extension_example.rotating_stellar_model;
export import mean_field_extension_example.ideal_gas_radiation;
import mean_field;
/*
* This file is the physics-facing composition layer. It contains no block
* matrices, generated residual types, Jacobian indices, or preconditioner
* plumbing. StellarModel infers those structural types from the four
* physical specifications passed to it.
*/
export namespace mean_field::extension_example {
struct RotatingStellarModelParameters final {
IdealGasRadiation::Parameters equationOfState;
mean_field::dimensions::PressureValue surfacePressure;
mean_field::dimensions::MassValue totalMass;
mean_field::dimensions::AngularMomentumValue totalAngularMomentum;
std::array<double, 3> rotationAxis{0.0, 0.0, 1.0};
std::array<double, 3> rotationCenter{0.0, 0.0, 0.0};
};
[[nodiscard]] auto makeRotatingStellarModel(const RotatingStellarModelParameters &parameters) {
return mean_field::model::StellarModel(
IdealGasRadiation(parameters.equationOfState),
mean_field::surface::Isobaric({.Psurf = parameters.surfacePressure}),
mean_field::integral::FixedTotalMass({.Mtotal = parameters.totalMass}),
mean_field::integral::FixedAngularMomentum({
.Jtotal = parameters.totalAngularMomentum,
.axis = parameters.rotationAxis,
.center = parameters.rotationCenter
})
);
}
using RotatingStellarModel = decltype(
makeRotatingStellarModel(std::declval<const RotatingStellarModelParameters &>())
);
static_assert(mean_field::model::StellarModelType<RotatingStellarModel>);
static_assert(RotatingStellarModel::symbolicallySquare);
/*
* Deliberate capability boundary:
*
* The specification above is a valid, strongly typed stellar model. The
* current numerical equilibrium core, however, closes density through a
* barotropic relation rho(h). This EOS instead needs an independent
* temperature or entropy field and its governing equation. Keeping this
* assertion false prevents an example from suggesting that discretize()
* already implements thermal equilibrium when it does not.
*/
inline constexpr bool currentEquilibriumBackendSupportsIdealGasRadiation =
mean_field::equilibrium::StellarEquilibriumModel<RotatingStellarModel>;
static_assert(!currentEquilibriumBackendSupportsIdealGasRadiation);
} // namespace mean_field::extension_example

View File

@@ -0,0 +1,292 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <limits>
#include <type_traits>
#include <utility>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import mean_field_extension_example.ideal_gas_radiation;
namespace {
namespace dimensions = mean_field::dimensions;
namespace eos = mean_field::eos;
namespace example = mean_field::extension_example;
[[nodiscard]] example::IdealGasRadiation makeSimpleEquationOfState() {
/* R = k_B / (mu m_u) = 12 / (2 * 3) = 2. */
return example::IdealGasRadiation({
.meanMolecularWeight = 2.0,
.boltzmannConstant = 12.0,
.atomicMassUnit = 3.0,
.radiationConstant = 9.0
});
}
template <typename Function>
[[nodiscard]] double centeredDifference(
Function function,
const double point
) {
const double step = std::cbrt(std::numeric_limits<double>::epsilon()) *
std::max(1.0, std::abs(point));
return (function(point + step) - function(point - step)) / (2.0 * step);
}
template <typename EquationOfState>
concept CanEvaluatePressureWithReversedInputs = requires(
const EquationOfState &equationOfState,
const dimensions::TemperatureValue temperature,
const dimensions::DensityValue density
) {
eos::evaluate<dimensions::quantity::Pressure>(equationOfState, temperature, density);
};
} // namespace
TEST_CASE("The extension satisfies the EOS protocol at compile time", "[extension-example][eos][type]") {
using EquationOfState = example::IdealGasRadiation;
STATIC_CHECK(mean_field::models::SelfDescribingModelSpecification<EquationOfState>);
STATIC_CHECK(eos::EquationOfStateModel<EquationOfState>);
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::PressureFromDensityAndTemperature>);
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::SpecificInternalEnergyFromDensityAndTemperature>);
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::SpecificEnthalpyFromDensityAndTemperature>);
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<EquationOfState>);
STATIC_CHECK_FALSE(CanEvaluatePressureWithReversedInputs<EquationOfState>);
using PressureResult = decltype(eos::evaluate<dimensions::quantity::Pressure>(
std::declval<const EquationOfState &>(),
dimensions::DensityValue{1.0},
dimensions::TemperatureValue{1.0}
));
STATIC_CHECK(std::same_as<PressureResult, dimensions::PressureValue>);
}
TEST_CASE("Gas and radiation terms reproduce the defining thermodynamics", "[extension-example][eos][physics]") {
const auto equationOfState = makeSimpleEquationOfState();
const dimensions::DensityValue density{4.0};
const dimensions::TemperatureValue temperature{2.0};
const auto pressureContributions = equationOfState.pressureContributions(density, temperature);
const auto pressure = eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
density,
temperature
);
const auto internalEnergy = eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
equationOfState,
density,
temperature
);
const auto enthalpy = eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
equationOfState,
density,
temperature
);
CHECK(equationOfState.specificGasConstant() == Catch::Approx(2.0));
CHECK(pressureContributions.gas.value() == Catch::Approx(16.0));
CHECK(pressureContributions.radiation.value() == Catch::Approx(48.0));
CHECK(pressure.value() == Catch::Approx(64.0));
CHECK(internalEnergy.value() == Catch::Approx(42.0));
CHECK(enthalpy.value() == Catch::Approx(58.0));
/* This is the thermodynamic identity h = u + P/rho. */
CHECK(enthalpy.value() == Catch::Approx(internalEnergy.value() + pressure.value() / density.value()));
}
TEST_CASE("The gas and photon terms have their expected scaling laws", "[extension-example][eos][physics]") {
const auto equationOfState = makeSimpleEquationOfState();
const dimensions::DensityValue density{3.5};
const dimensions::TemperatureValue temperature{1.25};
const auto baseline = equationOfState.pressureContributions(density, temperature);
const auto doubledDensity = equationOfState.pressureContributions(
dimensions::DensityValue{2.0 * density.value()},
temperature
);
const auto doubledTemperature = equationOfState.pressureContributions(
density,
dimensions::TemperatureValue{2.0 * temperature.value()}
);
CHECK(doubledDensity.gas.value() == Catch::Approx(2.0 * baseline.gas.value()));
CHECK(doubledDensity.radiation.value() == Catch::Approx(baseline.radiation.value()));
CHECK(doubledTemperature.gas.value() == Catch::Approx(2.0 * baseline.gas.value()));
CHECK(doubledTemperature.radiation.value() == Catch::Approx(16.0 * baseline.radiation.value()));
const double crossoverTemperature = std::cbrt(
3.0 * density.value() * equationOfState.specificGasConstant() /
equationOfState.parameters().radiationConstant
);
const auto crossover = equationOfState.pressureContributions(
density,
dimensions::TemperatureValue{crossoverTemperature}
);
CHECK(crossover.gas.value() == Catch::Approx(crossover.radiation.value()).epsilon(2.0e-14));
}
TEST_CASE("All declared Jacobian entries match centered numerical derivatives",
"[extension-example][eos][derivative][numerical]") {
const auto equationOfState = example::IdealGasRadiation({
.meanMolecularWeight = 1.25,
.boltzmannConstant = 2.75,
.atomicMassUnit = 0.8,
.radiationConstant = 0.35
});
struct State final {
double density;
double temperature;
};
const std::array states{
State{.density = 0.4, .temperature = 0.7},
State{.density = 2.0, .temperature = 1.5},
State{.density = 11.0, .temperature = 3.0}
};
for (const State state : states) {
const dimensions::DensityValue density{state.density};
const dimensions::TemperatureValue temperature{state.temperature};
const auto pressureDensity = eos::partialDerivative<
dimensions::quantity::Pressure,
dimensions::quantity::Density>(equationOfState, density, temperature);
const auto pressureTemperature = eos::partialDerivative<
dimensions::quantity::Pressure,
dimensions::quantity::Temperature>(equationOfState, density, temperature);
const auto energyDensity = eos::partialDerivative<
dimensions::quantity::SpecificInternalEnergy,
dimensions::quantity::Density>(equationOfState, density, temperature);
const auto energyTemperature = eos::partialDerivative<
dimensions::quantity::SpecificInternalEnergy,
dimensions::quantity::Temperature>(equationOfState, density, temperature);
const auto enthalpyDensity = eos::partialDerivative<
dimensions::quantity::SpecificEnthalpy,
dimensions::quantity::Density>(equationOfState, density, temperature);
const auto enthalpyTemperature = eos::partialDerivative<
dimensions::quantity::SpecificEnthalpy,
dimensions::quantity::Temperature>(equationOfState, density, temperature);
const double numericalPressureDensity = centeredDifference(
[&](const double rho) {
return eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
dimensions::DensityValue{rho},
temperature
).value();
},
state.density
);
const double numericalPressureTemperature = centeredDifference(
[&](const double T) {
return eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
density,
dimensions::TemperatureValue{T}
).value();
},
state.temperature
);
const double numericalEnergyDensity = centeredDifference(
[&](const double rho) {
return eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
equationOfState,
dimensions::DensityValue{rho},
temperature
).value();
},
state.density
);
const double numericalEnergyTemperature = centeredDifference(
[&](const double T) {
return eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
equationOfState,
density,
dimensions::TemperatureValue{T}
).value();
},
state.temperature
);
const double numericalEnthalpyDensity = centeredDifference(
[&](const double rho) {
return eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
equationOfState,
dimensions::DensityValue{rho},
temperature
).value();
},
state.density
);
const double numericalEnthalpyTemperature = centeredDifference(
[&](const double T) {
return eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
equationOfState,
density,
dimensions::TemperatureValue{T}
).value();
},
state.temperature
);
constexpr double tolerance = 3.0e-9;
CHECK(pressureDensity.value() == Catch::Approx(numericalPressureDensity).epsilon(tolerance));
CHECK(pressureTemperature.value() == Catch::Approx(numericalPressureTemperature).epsilon(tolerance));
CHECK(energyDensity.value() == Catch::Approx(numericalEnergyDensity).epsilon(tolerance));
CHECK(energyTemperature.value() == Catch::Approx(numericalEnergyTemperature).epsilon(tolerance));
CHECK(enthalpyDensity.value() == Catch::Approx(numericalEnthalpyDensity).epsilon(tolerance));
CHECK(enthalpyTemperature.value() == Catch::Approx(numericalEnthalpyTemperature).epsilon(tolerance));
}
}
TEST_CASE("The physical domain is checked at the EOS boundary", "[extension-example][eos][domain]") {
const auto equationOfState = makeSimpleEquationOfState();
const double nan = std::numeric_limits<double>::quiet_NaN();
CHECK_THROWS_AS(
example::IdealGasRadiation({
.meanMolecularWeight = 0.0,
.boltzmannConstant = 1.0,
.atomicMassUnit = 1.0,
.radiationConstant = 1.0
}),
std::invalid_argument
);
CHECK_THROWS_AS(
example::IdealGasRadiation({
.meanMolecularWeight = 1.0,
.boltzmannConstant = 1.0,
.atomicMassUnit = 1.0,
.radiationConstant = -1.0
}),
std::invalid_argument
);
CHECK_THROWS_AS(
eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
dimensions::DensityValue{0.0},
dimensions::TemperatureValue{1.0}
),
eos::EvaluationError
);
CHECK_THROWS_AS(
eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
dimensions::DensityValue{1.0},
dimensions::TemperatureValue{-1.0}
),
eos::EvaluationError
);
CHECK_THROWS_AS(
eos::evaluate<dimensions::quantity::Pressure>(
equationOfState,
dimensions::DensityValue{nan},
dimensions::TemperatureValue{1.0}
),
eos::EvaluationError
);
}

View File

@@ -0,0 +1,67 @@
#include <concepts>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import mean_field_extension_example.rotating_stellar_model;
TEST_CASE("The example EOS composes with existing stellar specifications",
"[extension-example][model][type]") {
using namespace mean_field;
namespace example = mean_field::extension_example;
const auto stellarModel = example::makeRotatingStellarModel({
.equationOfState = {
.meanMolecularWeight = 0.62,
.boltzmannConstant = 1.380649e-16,
.atomicMassUnit = 1.66053906660e-24,
.radiationConstant = 7.5657e-15
},
.surfacePressure = dimensions::PressureValue{0.0},
.totalMass = dimensions::MassValue{1.75},
.totalAngularMomentum = dimensions::AngularMomentumValue{0.3},
.rotationAxis = {0.0, 0.0, 4.0},
.rotationCenter = {0.1, -0.2, 0.3}
});
using Model = std::remove_cvref_t<decltype(stellarModel)>;
STATIC_CHECK(std::same_as<Model, example::RotatingStellarModel>);
STATIC_CHECK(model::StellarModelType<Model>);
STATIC_CHECK(Model::symbolicallySquare);
STATIC_CHECK(Model::specificationCount == 4);
STATIC_CHECK(std::same_as<model::EquationOfStateType<Model>, example::IdealGasRadiation>);
STATIC_CHECK(Model::template containsSpecification<integral::FixedTotalMass>);
STATIC_CHECK(Model::template containsSpecification<integral::FixedAngularMomentum>);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::constitutive_law> == 1);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::boundary_condition> == 1);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::invariant> == 2);
CHECK(stellarModel.equationOfState().parameters().meanMolecularWeight == 0.62);
CHECK(stellarModel.surfaceCondition().targetPressure() == dimensions::PressureValue{0.0});
CHECK(stellarModel.specification<integral::FixedTotalMass>().targetMass() == dimensions::MassValue{1.75});
const auto &angularMomentum = stellarModel.specification<integral::FixedAngularMomentum>();
CHECK(angularMomentum.targetAngularMomentum() == dimensions::AngularMomentumValue{0.3});
CHECK(angularMomentum.axis()[0] == 0.0);
CHECK(angularMomentum.axis()[1] == 0.0);
CHECK(angularMomentum.axis()[2] == 1.0);
CHECK(angularMomentum.center()[0] == 0.1);
CHECK(angularMomentum.center()[1] == -0.2);
CHECK(angularMomentum.center()[2] == 0.3);
CHECK(stellarModel.runtimeSpecificationDescriptors().size() == 4);
}
TEST_CASE("The example states the current thermal-runtime boundary explicitly",
"[extension-example][model][capability]") {
using Model = mean_field::extension_example::RotatingStellarModel;
/*
* This is not a failure of model composition. It is the intended
* compile-time rejection of a thermal EOS by a currently barotropic
* numerical core. See the manual section 'What compiles today'.
*/
STATIC_CHECK(mean_field::model::StellarModelType<Model>);
STATIC_CHECK_FALSE(mean_field::extension_example::currentEquilibriumBackendSupportsIdealGasRadiation);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<Model>);
}

4
format
View File

@@ -1,3 +1,3 @@
#!/bin/bash
find libmeanfield tests -type f \( -name '*.cpp' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}
find libmeanfield tests -type f \( -name '*.cppm' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}
find libmeanfield tests experiments -type f \( -name '*.cpp' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}
find libmeanfield tests experiments -type f \( -name '*.cppm' \) | xargs -I{} clang-format -style=file:clang-format-styles/style -i {}

View File

@@ -1,4 +1,5 @@
module;
#include "profile.h"
#include <array>
#include <mfem.hpp>
@@ -6,6 +7,35 @@ module mean_field;
import :mapping.coefficients;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
mfem::Array<int> make_domain_marker(
const mfem::Mesh &mesh,
const mean_field::utils::DOMAINS domain
) {
switch (domain) {
case mean_field::utils::DOMAINS::CORE:
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Core, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ENVELOPE:
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Envelope, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::ALL:
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::All, DomainSchema>(mesh);
case mean_field::utils::DOMAINS::STELLAR:
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Stellar, DomainSchema>(
mesh
);
case mean_field::utils::DOMAINS::VACUUM:
return mean_field::utils::domain::make_attribute_marker<mean_field::utils::domain::Vacuum, DomainSchema>(
mesh
);
}
MFEM_ABORT("Unsupported integration domain.");
}
template <typename FormT>
const mfem::IntegrationRule &get_density_rule(
const mean_field::fem::FEM &fem,
@@ -13,23 +43,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<mean_field::field::Density>;
using DensityField = mean_field::field::Field<mean_field::field::Density>;
const mean_field::quadrature::Query query =
DensityField::make_query<FormT>(
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<FormT>(
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
@@ -40,21 +63,20 @@ namespace mean_field::analysis {
utils::DOMAINS domain,
mapping::COORDINATE_SPACE coord_space
) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::domain_integrate_grid_function", 0);
mfem::LinearForm lf(fem.densityFes.get());
mfem::GridFunctionCoefficient gf_c(&gf);
double local_integral;
mfem::Array<int> elem_markers;
populate_element_mask(fem.mesh.get(), domain, elem_markers);
const mfem::ElementTransformation &representative_transformation =
*fem.mesh->GetElementTransformation(0);
mfem::Array<int> elem_markers = make_domain_marker(*fem.mesh, domain);
const mfem::ElementTransformation &representative_transformation = *fem.mesh->GetElementTransformation(0);
const mfem::IntegrationRule &integration_rule =
get_density_rule<field::Density::Form::MassConservation>(
fem, representative_transformation, {}, domain
);
get_density_rule<field::Density::Form::MassConservation>(fem, representative_transformation, {}, domain);
if (fem.has_mapping() &&
coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
mapping::MappedScalarCoefficient mapped_gf_c(*fem.mapping, gf_c);
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
mapping::MappedScalarCoefficient mapped_gf_c(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, gf_c
);
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM
// takes ownership so memory is not leaked
@@ -80,10 +102,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;
}
@@ -91,18 +110,23 @@ namespace mean_field::analysis {
const fem::FEM &fem,
const mfem::GridFunction &rho
) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_com", 0);
std::uint64_t mapping_evaluations = 0;
const int dim = fem.mesh->Dimension();
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Vector local_com(dim);
mapping::VolumeMappingContext mapping_context;
local_com = 0.0;
double local_mass = 0.0;
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3)
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans =
fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir =
get_density_rule<field::Density::Form::CenterOfMass>(
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
const mfem::IntegrationRule &ir = get_density_rule<field::Density::Form::CenterOfMass>(
fem, *trans, std::array<int, 1>{1}, utils::DOMAINS::STELLAR
);
@@ -110,18 +134,15 @@ namespace mean_field::analysis {
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight;
if (fem.has_mapping()) {
weight *= fem.mapping->ComputeDetJ(*trans, ip);
}
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Center-of-mass integration encountered an invalid mapping."
);
++mapping_evaluations;
const double weight = mapping_context.quadrature.weight;
double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim);
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
const double mass_term = rho_val * weight;
local_mass += mass_term;
@@ -132,17 +153,24 @@ namespace mean_field::analysis {
}
}
double global_mass = 0.0;
mfem::Vector global_com(dim);
MPI_Comm comm = fem.mesh->GetComm();
MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm);
MEAN_FIELD_PROFILE_COUNT("analysis::get_com mapping evaluations", mapping_evaluations);
mfem::Vector local_integrals(dim + 1);
mfem::Vector global_integrals(dim + 1);
local_integrals(0) = local_mass;
for (int d = 0; d < dim; ++d) {
local_integrals(d + 1) = local_com(d);
}
MPI_Allreduce(
local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM,
comm
local_integrals.GetData(), global_integrals.GetData(), dim + 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
);
const double global_mass = global_integrals(0);
mfem::Vector global_com(dim);
for (int d = 0; d < dim; ++d) {
global_com(d) = global_integrals(d + 1);
}
if (global_mass > 1e-18) {
global_com /= global_mass;
} else {
@@ -157,9 +185,9 @@ 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
);
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::conserve_mass", 0);
if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR);
current_mass > 1e-15)
rho *= (target_mass / current_mass);
}
@@ -168,15 +196,14 @@ 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);
};
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_moment_of_inertia", 0);
auto s2_func = [](const mfem::Vector &x) { return std::pow(x(0), 2) + std::pow(x(1), 2); };
std::unique_ptr<mfem::Coefficient> s2_coeff;
if (fem.has_mapping()) {
s2_coeff =
std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.mapping, s2_func
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, s2_func
);
} else {
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
@@ -186,22 +213,17 @@ 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<field::Density::Form::Quadrupole>(
fem, representative_transformation, std::array<int, 1>{2},
utils::DOMAINS::STELLAR
);
mfem::Array<int> 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<field::Density::Form::Quadrupole>(
fem, representative_transformation, std::array<int, 1>{2}, utils::DOMAINS::STELLAR
);
mfem::Array<int> stellar_markers =
utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*fem.mesh);
double local_I = 0.0;
if (fem.has_mapping()) {
mapping::MappedScalarCoefficient mapped_integrand(
*fem.mapping, I_integrand
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate, I_integrand
);
auto *integrator = new mfem::DomainLFIntegrator(mapped_integrand);
integrator->SetIntRule(&integration_rule);
@@ -217,9 +239,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;
}
@@ -228,39 +248,33 @@ namespace mean_field::analysis {
const mapping::COORDINATE_SPACE coordinate_space,
const utils::DOMAINS domain
) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_mesh_volume", 0);
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;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mapping::VolumeMappingContext mapping_context;
for (int e = 0; e < mesh.GetNE(); ++e) {
const int attr = mesh.GetAttribute(e);
switch (domain) {
case utils::DOMAINS::ALL:
break;
case utils::DOMAINS::STELLAR:
if (attr == 3)
const bool selected = domain == utils::DOMAINS::ALL ||
(domain == utils::DOMAINS::STELLAR &&
DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(attr)) ||
(domain == utils::DOMAINS::VACUUM &&
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr));
if (!selected)
continue;
break;
case utils::DOMAINS::VACUUM:
if (attr != 3)
continue;
break;
default:
MFEM_ABORT("Unsupported domain type for volume computation.");
}
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
const mfem::IntegrationRule &ir =
get_density_rule<field::Density::Form::MassConservation>(
fem, *T, {}, domain
);
get_density_rule<field::Density::Form::MassConservation>(fem, *T, {}, domain);
for (int q = 0; q < ir.GetNPoints(); ++q) {
const mfem::IntegrationPoint &ip = ir.IntPoint(q);
@@ -269,7 +283,11 @@ namespace mean_field::analysis {
double dV = ip.weight * T->Weight();
if (physical) {
dV *= std::fabs(fem.mapping->ComputeDetJ(*T, ip));
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) == mapping::MappingStatus::valid,
"Mesh-volume integration encountered an invalid mapping."
);
dV = mapping_context.quadrature.weight;
}
local_volume += dV;
@@ -277,10 +295,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

View File

@@ -0,0 +1,345 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :deformation.nodal_radial_surface;
namespace mean_field::deformation {
namespace {
[[nodiscard]] SurfaceDeformationDescriptor nodalRadialDescriptor(const int spatialDimension) noexcept {
return {
.name = "NodalRadialSurface",
.spatialDimension = spatialDimension,
.motionKind = SurfaceMotionKind::Radial,
.linearOnReferenceGeometry = true,
.requiresStarShapedReferenceSurface = true,
.hasExactDerivativeTranspose = true,
.hasExactPullbackDerivative = true,
.translationTreatment = GeometricGaugeTreatment::Retained,
.orientationTreatment = GeometricGaugeTreatment::Retained
};
}
void requireFiniteVector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
throw std::invalid_argument(message);
}
}
}
} // namespace
SurfaceDeformationCompilationContext::SurfaceDeformationCompilationContext(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
field::ScalarBoundaryDofMap surfaceDofMap
)
: m_scalarFiniteElementSpace(&scalarFiniteElementSpace),
m_surfaceDofMap(std::move(surfaceDofMap)) {
if (scalarFiniteElementSpace.Nonconforming()) {
throw std::invalid_argument(
"Surface deformation compilation currently requires a conforming scalar finite-element space."
);
}
if (scalarFiniteElementSpace.GetVDim() != 1) {
throw std::invalid_argument("Surface deformation compilation requires a scalar finite-element space.");
}
if (scalarFiniteElementSpace.GetMesh() == nullptr) {
throw std::invalid_argument("Surface deformation compilation requires a finite-element mesh.");
}
if (m_surfaceDofMap.volume_true_dof_size() != scalarFiniteElementSpace.GetTrueVSize()) {
throw std::invalid_argument(
"The surface DOF map and scalar finite-element space have incompatible true-DOF sizes."
);
}
if (m_surfaceDofMap.global_size() <= 0) {
throw std::invalid_argument("Surface deformation compilation requires at least one surface coordinate.");
}
}
mfem::ParFiniteElementSpace &SurfaceDeformationCompilationContext::scalarFiniteElementSpace() const noexcept {
return *m_scalarFiniteElementSpace;
}
const field::ScalarBoundaryDofMap &SurfaceDeformationCompilationContext::surfaceDofMap() const noexcept {
return m_surfaceDofMap;
}
NodalRadialSurface::NodalRadialSurface(mfem::Vector referenceCenter)
: m_referenceCenter(std::move(referenceCenter)) {
validate();
}
const mfem::Vector &NodalRadialSurface::referenceCenter() const noexcept {
return m_referenceCenter;
}
SurfaceDeformationDescriptor NodalRadialSurface::descriptor() const noexcept {
return nodalRadialDescriptor(m_referenceCenter.Size());
}
void NodalRadialSurface::validate() const {
if (m_referenceCenter.Size() <= 0) {
throw std::invalid_argument("NodalRadialSurface requires a non-empty reference center.");
}
requireFiniteVector(m_referenceCenter, "NodalRadialSurface reference-center coordinates must be finite.");
}
PreparedNodalRadialSurface::PreparedNodalRadialSurface(
const SurfaceDeformationDescriptor descriptor,
mfem::Vector referenceCenter,
field::ScalarBoundaryDofMap surfaceDofMap,
mfem::Vector radialDirections,
mfem::Vector referenceRadii
)
: m_descriptor(descriptor),
m_referenceCenter(std::move(referenceCenter)),
m_surfaceDofMap(std::move(surfaceDofMap)),
m_radialDirections(std::move(radialDirections)),
m_referenceRadii(std::move(referenceRadii)) {
}
SurfaceDeformationDescriptor PreparedNodalRadialSurface::descriptor() const noexcept {
return m_descriptor;
}
int PreparedNodalRadialSurface::parameterCount() const noexcept {
return m_surfaceDofMap.local_size();
}
long long PreparedNodalRadialSurface::globalParameterCount() const noexcept {
return m_surfaceDofMap.global_size();
}
long long PreparedNodalRadialSurface::globalParameterOffset() const noexcept {
return m_surfaceDofMap.global_offset();
}
int PreparedNodalRadialSurface::spatialDimension() const noexcept {
return m_descriptor.spatialDimension;
}
int PreparedNodalRadialSurface::surfaceDisplacementSize() const noexcept {
return spatialDimension() * parameterCount();
}
long long PreparedNodalRadialSurface::globalSurfaceDisplacementSize() const noexcept {
return static_cast<long long>(spatialDimension()) * globalParameterCount();
}
long long PreparedNodalRadialSurface::globalSurfaceDisplacementOffset() const noexcept {
return static_cast<long long>(spatialDimension()) * globalParameterOffset();
}
int PreparedNodalRadialSurface::surfaceDisplacementDof(
const int parameterDof,
const int component
) const {
if (parameterDof < 0 || parameterDof >= parameterCount()) {
throw std::out_of_range("Parameter DOF is outside PreparedNodalRadialSurface.");
}
if (component < 0 || component >= spatialDimension()) {
throw std::out_of_range("Surface-displacement component is outside PreparedNodalRadialSurface.");
}
return spatialDimension() * parameterDof + component;
}
double PreparedNodalRadialSurface::radialDirection(
const int parameterDof,
const int component
) const {
return m_radialDirections(surfaceDisplacementDof(parameterDof, component));
}
double PreparedNodalRadialSurface::referenceRadius(const int parameterDof) const {
if (parameterDof < 0 || parameterDof >= parameterCount()) {
throw std::out_of_range("Parameter DOF is outside PreparedNodalRadialSurface.");
}
return m_referenceRadii(parameterDof);
}
const mfem::Vector &PreparedNodalRadialSurface::referenceCenter() const noexcept {
return m_referenceCenter;
}
const field::ScalarBoundaryDofMap &PreparedNodalRadialSurface::surfaceDofMap() const noexcept {
return m_surfaceDofMap;
}
void PreparedNodalRadialSurface::buildSurfaceDisplacement(
const mfem::Vector &parameters,
mfem::Vector &surfaceDisplacement
) const {
requireParameterSize(parameters);
requireSurfaceDisplacementSize(surfaceDisplacement);
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
for (int component = 0; component < spatialDimension(); ++component) {
const int surfaceDof = spatialDimension() * parameterDof + component;
surfaceDisplacement(surfaceDof) = parameters(parameterDof) * m_radialDirections(surfaceDof);
}
}
}
void PreparedNodalRadialSurface::applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &surfaceDisplacementDirection
) const {
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireSurfaceDisplacementSize(surfaceDisplacementDirection);
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
for (int component = 0; component < spatialDimension(); ++component) {
const int surfaceDof = spatialDimension() * parameterDof + component;
surfaceDisplacementDirection(surfaceDof) =
parameterDirection(parameterDof) * m_radialDirections(surfaceDof);
}
}
}
void PreparedNodalRadialSurface::applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &surfaceDisplacementDual,
mfem::Vector &parameterDual
) const {
requireParameterSize(parameters);
requireSurfaceDisplacementSize(surfaceDisplacementDual);
requireParameterSize(parameterDual);
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
double radialWork = 0.0;
for (int component = 0; component < spatialDimension(); ++component) {
const int surfaceDof = spatialDimension() * parameterDof + component;
radialWork += m_radialDirections(surfaceDof) * surfaceDisplacementDual(surfaceDof);
}
parameterDual(parameterDof) = radialWork;
}
}
void PreparedNodalRadialSurface::applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &surfaceDisplacementDual,
mfem::Vector &parameterDualAction
) const {
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireSurfaceDisplacementSize(surfaceDisplacementDual);
requireParameterSize(parameterDualAction);
parameterDualAction = 0.0;
}
void PreparedNodalRadialSurface::requireParameterSize(const mfem::Vector &parameters) const {
if (parameters.Size() != parameterCount()) {
throw std::invalid_argument(
std::format(
"Nodal radial parameter vector has size {}, but the prepared surface requires {}.",
parameters.Size(), parameterCount()
)
);
}
}
void PreparedNodalRadialSurface::requireSurfaceDisplacementSize(const mfem::Vector &surfaceDisplacement) const {
if (surfaceDisplacement.Size() != surfaceDisplacementSize()) {
throw std::invalid_argument(
std::format(
"Surface displacement vector has size {}, but the prepared nodal radial surface requires {}.",
surfaceDisplacement.Size(), surfaceDisplacementSize()
)
);
}
}
PreparedNodalRadialSurface compileSurfaceDeformationPrescription(
const NodalRadialSurface &prescription,
const SurfaceDeformationCompilationContext &context
) {
prescription.validate();
mfem::ParFiniteElementSpace &scalarSpace = context.scalarFiniteElementSpace();
const mfem::Mesh *mesh = scalarSpace.GetMesh();
if (mesh == nullptr) {
throw std::invalid_argument("Nodal radial surface compilation requires a reference mesh.");
}
if (prescription.referenceCenter().Size() != mesh->SpaceDimension()) {
throw std::invalid_argument(
std::format(
"NodalRadialSurface reference center has dimension {}, but the reference mesh has spatial "
"dimension {}.",
prescription.referenceCenter().Size(), mesh->SpaceDimension()
)
);
}
const field::ScalarBoundaryDofMap &surfaceDofMap = context.surfaceDofMap();
const int parameterCount = surfaceDofMap.local_size();
const int spatialDimension = mesh->SpaceDimension();
mfem::Vector referencePositions(spatialDimension * parameterCount);
mfem::ParGridFunction coordinateField(&scalarSpace);
for (int component = 0; component < spatialDimension; ++component) {
mfem::FunctionCoefficient coordinateCoefficient([component](const mfem::Vector &position) {
return position(component);
});
coordinateField.ProjectCoefficient(coordinateCoefficient);
mfem::Vector coordinateTrueDofs;
coordinateField.GetTrueDofs(coordinateTrueDofs);
const mfem::Vector surfaceCoordinates = surfaceDofMap.gather(coordinateTrueDofs);
for (int parameterDof = 0; parameterDof < parameterCount; ++parameterDof) {
referencePositions(spatialDimension * parameterDof + component) = surfaceCoordinates(parameterDof);
}
}
mfem::Vector radialDirections(referencePositions.Size());
mfem::Vector referenceRadii(parameterCount);
for (int parameterDof = 0; parameterDof < parameterCount; ++parameterDof) {
double radiusSquared = 0.0;
for (int component = 0; component < spatialDimension; ++component) {
const int surfaceDof = spatialDimension * parameterDof + component;
const double radialCoordinate =
referencePositions(surfaceDof) - prescription.referenceCenter()(component);
radialDirections(surfaceDof) = radialCoordinate;
radiusSquared += radialCoordinate * radialCoordinate;
}
const double radius = std::sqrt(radiusSquared);
if (!std::isfinite(radius) || radius <= 0.0) {
throw std::invalid_argument(
"Every nodal radial surface coordinate must have a finite positive distance from the reference "
"center."
);
}
referenceRadii(parameterDof) = radius;
for (int component = 0; component < spatialDimension; ++component) {
radialDirections(spatialDimension * parameterDof + component) /= radius;
}
}
return PreparedNodalRadialSurface(
nodalRadialDescriptor(spatialDimension), prescription.referenceCenter(), surfaceDofMap,
std::move(radialDirections), std::move(referenceRadii)
);
}
} // namespace mean_field::deformation

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ namespace mean_field::fem {
using DisplacementVector = field::Displacement::Vector;
using DensityScalar = field::Density::Scalar;
using EnthalpyScalar = field::Enthalpy::Scalar;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
// =====================================================================
// Section 1: Mesh construction
@@ -44,19 +45,33 @@ namespace mean_field::fem {
stroid::refinement::UniformRefinement(fem.smesh, extraRefine);
}
if (fem.smesh.mesh == nullptr || fem.smesh.reference_mesh == nullptr) {
throw std::runtime_error("A STROID mesh requires paired physical and logical reference meshes.");
}
int mpiSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &mpiSize);
const std::unique_ptr<int[]> meshPartitioning(
fem.smesh.mesh->GeneratePartitioning(mpiSize, 1)
);
const std::unique_ptr<int[]> meshPartitioning(fem.smesh.mesh->GeneratePartitioning(mpiSize, 1));
fem.mesh = std::make_unique<mfem::ParMesh>(
MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1
);
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh, meshPartitioning.get(), 1);
fem.logicalReferenceMesh =
std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.reference_mesh, meshPartitioning.get(), 1);
fem.mesh->EnsureNodes();
if (fem.logicalReferenceMesh->GetNE() != fem.mesh->GetNE()) {
throw std::runtime_error("The physical and logical reference meshes have incompatible local elements.");
}
for (int element = 0; element < fem.mesh->GetNE(); ++element) {
if (fem.logicalReferenceMesh->GetElementGeometry(element) != fem.mesh->GetElementGeometry(element) ||
fem.logicalReferenceMesh->GetAttribute(element) != fem.mesh->GetAttribute(element)) {
throw std::runtime_error(
"The physical and logical reference meshes do not preserve element correspondence."
);
}
}
// =====================================================================
// Section 2: Exterior compactification coordinate
// =====================================================================
@@ -73,11 +88,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 +107,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,35 +117,25 @@ namespace mean_field::fem {
);
}
const int compactificationOrder =
serialCoordinateSpace.GetMaxElementOrder();
const int compactificationOrder = serialCoordinateSpace.GetMaxElementOrder();
const int dimension = fem.mesh->Dimension();
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(
compactificationOrder, dimension
);
fem.compactificationFec = std::make_unique<mfem::H1_FECollection>(compactificationOrder, dimension);
fem.compactificationFes = std::make_unique<mfem::ParFiniteElementSpace>(
fem.mesh.get(), fem.compactificationFec.get()
);
fem.compactificationFes =
std::make_unique<mfem::ParFiniteElementSpace>(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<mfem::ParGridFunction>(
fem.compactificationFes.get()
);
fem.compactificationCoordinate = std::make_unique<mfem::ParGridFunction>(fem.compactificationFes.get());
*fem.compactificationCoordinate = distributedCoordinate;
@@ -142,14 +143,11 @@ namespace mean_field::fem {
double localMaximum = -std::numeric_limits<double>::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 +158,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,12 +179,9 @@ namespace mean_field::fem {
// Gravity potential: scalar L2
// ---------------------------------------------------------------------
fem.gravityPotentialFec =
GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFec = GravityField::make_fec<GravityPotential>(dimension);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(
*fem.mesh, *fem.gravityPotentialFec
);
fem.gravityPotentialFes = GravityField::make_fespace<GravityPotential>(*fem.mesh, *fem.gravityPotentialFec);
// ---------------------------------------------------------------------
// Gravity flux: H(div)/RT. Basis choices are encoded by field.mfem.
@@ -201,36 +189,38 @@ namespace mean_field::fem {
fem.gravityFluxFec = GravityField::make_fec<GravityFlux>(dimension);
fem.gravityFluxFes = GravityField::make_fespace<GravityFlux>(
*fem.mesh, *fem.gravityFluxFec
);
fem.gravityFluxFes = GravityField::make_fespace<GravityFlux>(*fem.mesh, *fem.gravityFluxFec);
// ---------------------------------------------------------------------
// Displacement: vector H1. Ordering is encoded by field.mfem.
// ---------------------------------------------------------------------
fem.displacementFec =
DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFec = DisplacementField::make_fec<DisplacementVector>(dimension);
fem.displacementFes =
DisplacementField::make_fespace<DisplacementVector>(
*fem.mesh, *fem.displacementFec
);
fem.displacementFes = DisplacementField::make_fespace<DisplacementVector>(*fem.mesh, *fem.displacementFec);
fem.displacement =
std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
fem.displacement = std::make_unique<mfem::ParGridFunction>(fem.displacementFes.get());
*fem.displacement = 0.0;
// ---------------------------------------------------------------------
// Surface deformation: scalar H1 coordinates on StellarSurface.
//
// This ambient scalar space exists only to define the surface basis
// and owned true-DOF topology. Interior scalar DOFs are not nonlinear
// unknowns.
// ---------------------------------------------------------------------
fem.surfaceDeformationFes =
std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.displacementFec.get());
// ---------------------------------------------------------------------
// Density: scalar discontinuous L2
// ---------------------------------------------------------------------
fem.densityFec = DensityField::make_fec<DensityScalar>(dimension);
fem.densityFes = DensityField::make_fespace<DensityScalar>(
*fem.mesh, *fem.densityFec
);
fem.densityFes = DensityField::make_fespace<DensityScalar>(*fem.mesh, *fem.densityFec);
// ---------------------------------------------------------------------
// Specific enthalpy: scalar continuous H1
@@ -238,60 +228,10 @@ namespace mean_field::fem {
fem.enthalpyFec = EnthalpyField::make_fec<EnthalpyScalar>(dimension);
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(
*fem.mesh, *fem.enthalpyFec
);
fem.enthalpyFes = EnthalpyField::make_fespace<EnthalpyScalar>(*fem.mesh, *fem.enthalpyFec);
// =====================================================================
// Section 4: Domain mapping
// =====================================================================
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."
);
}
)
.value();
fem.mapping = std::make_unique<mapping::DomainMapper>(
*fem.displacement, stellarRadiusReference, infinityRadiusReference
);
// =====================================================================
// Section 5: Block offsets
//
// Legacy layouts only. New coupled operators use :utils.blocks forms.
//
// Main system: [Displacement | Density]
// Gravity system: [Flux | Potential]
// =====================================================================
fem.blockTrueOffsets.SetSize(3);
fem.blockTrueOffsets[0] = 0;
fem.blockTrueOffsets[1] = fem.displacementFes->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();
// =====================================================================
// Section 6: Multipole data
// Section 4: Multipole data
// =====================================================================
fem.com.SetSize(dimension);
@@ -301,16 +241,9 @@ namespace mean_field::fem {
fem.Q = 0.0;
// =====================================================================
// Section 7: Essential boundaries and domain masks
// Section 5: Boundary markers
// =====================================================================
fem.essentialDisplacementTdofs.SetSize(0);
populate_element_mask(
fem.mesh.get(), utils::DOMAINS::STELLAR,
fem.gravityContext.stellar_mask
);
const int boundaryAttributeCount = fem.mesh->bdr_attributes.Max();
fem.boundaryContext.inf_bounds.SetSize(boundaryAttributeCount);
@@ -320,108 +253,41 @@ namespace mean_field::fem {
fem.boundaryContext.inf_bounds = 0;
fem.boundaryContext.stellar_bounds = 0;
fem.boundaryContext.inf_bounds
[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
fem.boundaryContext.stellar_bounds
[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
fem.boundaryContext.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
// =====================================================================
// Section 8: Gravity solver context
// Section 7: Quadrature policy
// =====================================================================
fem.gravityContext.minres =
std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
const quadrature::QuadratureOptions &quadratureOptions = args.quadrature;
fem.gravityContext.minres->SetRelTol(1.0e-12);
fem.gravityContext.minres->SetAbsTol(1.0e-12);
fem.gravityContext.minres->SetMaxIter(1000);
fem.gravityContext.minres->SetPrintLevel(0);
fem.gravityContext.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
fem.gravityContext.prec_Phi->SetPrintLevel(0);
fem.gravityContext.block_prec =
std::make_unique<mfem::BlockDiagonalPreconditioner>(
fem.gravityBlockTrueOffsets
);
fem.gravityContext.minres->SetPreconditioner(
*fem.gravityContext.block_prec
);
// =====================================================================
// Section 9: Vacuum true-DOF masks
// =====================================================================
{
mfem::Array<int> 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.densityFes.get(), vacuumMask, fem.vacuumDensityTdofs
);
utils::populate_domain_tdofs(
fem.enthalpyFes.get(), vacuumMask, fem.vacuumEnthalpyTdofs
);
if (quadratureOptions.validation.reject_negative_boosts && quadratureOptions.global_boost < 0) {
throw std::invalid_argument("Global quadrature boost cannot be negative.");
}
// =====================================================================
// Section 10: Quadrature policy
// =====================================================================
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."
);
}
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](
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 (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;
@@ -431,129 +297,74 @@ namespace mean_field::fem {
}
};
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::RuleFactory>(
quadrature::Policy(std::move(quadratureRuleSet))
);
apply_quadrature_options(quadratureRuleSet.roles.projection, quadratureOptions.roles.projection);
fem.quadratureFactory =
std::make_unique<quadrature::RuleFactory>(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<const mapping::compactification::KelvinCompactification>(args.kelvin_options);
MFEM_VERIFY(
args.domain_mapper_options.vacuum_element_attribute ==
DomainSchema::template material_attribute<utils::domain::Vacuum>(),
"The domain-mapper compactification attribute must match the vacuum "
"material registered by the "
"production domain schema."
);
fem.domainMapperStateless =
std::make_unique<mapping::DomainMapperStateless>(
args.domain_mapper_options, std::move(exteriorDomain)
);
std::make_unique<mapping::DomainMapper>(args.domain_mapper_options, std::move(exteriorDomain));
return fem;
}

View File

@@ -4,8 +4,16 @@ module;
module mean_field;
namespace mean_field::integrators {
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map)
: m_map(map) {
AdvectionIntegrator::AdvectionIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void AdvectionIntegrator::AssembleElementVector(
@@ -14,6 +22,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
@@ -39,14 +49,13 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref);
@@ -83,8 +92,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;
}
}
}
@@ -96,6 +104,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
@@ -117,14 +127,13 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_v->CalcDShape(ip, dshape_v_ref);
@@ -171,8 +180,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 +195,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;
}
}
}

View File

@@ -4,10 +4,16 @@ module mean_field;
namespace mean_field::integrators {
CentrifugalForceIntegrator::CentrifugalForceIntegrator(
const mapping::DomainMapper &map,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
)
: m_map(map),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(3) {
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
m_omega = omega;
@@ -18,9 +24,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;
}
@@ -30,6 +34,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
@@ -52,8 +58,8 @@ namespace mean_field::integrators {
}
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::Vector x_phys(dim);
mfem::Vector a(dim), b(dim);
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
m_ir, "CentrifugalForceIntegrator must be configured with an "
@@ -66,12 +72,17 @@ namespace mean_field::integrators {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
const mapping::MappingStatus mapping_status = m_mapping.EvaluateVolume(Tr, ip, mapping_context);
MFEM_VERIFY(
mapping_status == mapping::MappingStatus::valid,
"Centrifugal-force assembly encountered an invalid volume mapping."
);
const double weight = mapping_context.quadrature.weight;
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
m_map.GetPhysicalPoint(Tr, ip, x_phys);
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
// ω x r
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
@@ -102,6 +113,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elmats)) {
return;
}
@@ -127,22 +140,26 @@ namespace mean_field::integrators {
return;
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
mfem::Vector x_phys(dim);
mfem::Vector a(dim), b(dim);
mapping::VolumeMappingContext mapping_context;
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);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
const mapping::MappingStatus mapping_status = m_mapping.EvaluateVolume(Tr, ip, mapping_context);
MFEM_VERIFY(
mapping_status == mapping::MappingStatus::valid,
"Centrifugal-force Jacobian assembly encountered an invalid volume mapping."
);
const double weight = mapping_context.quadrature.weight;
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
m_map.GetPhysicalPoint(Tr, ip, x_phys);
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
// ω x r
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
@@ -159,8 +176,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;
}
}
}

View File

@@ -5,10 +5,16 @@ module mean_field;
namespace mean_field::integrators {
CoriolisIntegrator::CoriolisIntegrator(
const mapping::DomainMapper &map,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
)
: m_map(map),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_omega(omega) {
m_omega_mat.SetSize(3, 3);
m_omega_mat = 0.0;
@@ -26,6 +32,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
@@ -49,14 +57,13 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
@@ -78,8 +85,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;
}
}
}
@@ -91,6 +97,7 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
@@ -111,14 +118,13 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
@@ -146,9 +152,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 +165,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;
}
}
}

View File

@@ -6,39 +6,36 @@ 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 {
GravityMomentumIntegrator::GravityMomentumIntegrator(
const mapping::DomainMapper &map,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const GravityForceJacobianMode jacobian_mode
)
: m_map(map),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
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;
}
@@ -48,23 +45,22 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
MFEM_VERIFY(
m_integration_rule,
"GravityForceIntegrator must be configured with an "
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 "
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 "
elfun.Size() > gravity_gradient_block, "GravityForceIntegrator requires velocity, density, and "
"gravity-gradient element states."
);
MFEM_VERIFY(
@@ -72,8 +68,7 @@ namespace mean_field::integrators {
"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(
@@ -83,22 +78,18 @@ 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 *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 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 &gravity_gradient_dofs = *elfun[gravity_gradient_block];
MFEM_VERIFY(
density_dofs.Size() == density_dofs_count,
"GravityForceIntegrator received an incorrectly sized density "
density_dofs.Size() == density_dofs_count, "GravityForceIntegrator received an incorrectly sized density "
"state."
);
MFEM_VERIFY(
@@ -123,40 +114,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_mapping.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 +148,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;
}
}
}
@@ -180,28 +160,26 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elmats)) {
return;
}
MFEM_VERIFY(
m_integration_rule,
"GravityForceIntegrator must be configured with an "
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 "
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 "
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(
@@ -221,29 +199,25 @@ namespace mean_field::integrators {
MFEM_ABORT(
"Exact GravityForceIntegrator geometry Jacobian is unavailable "
"until "
"DomainMapper linearization is "
"implemented."
"the stateless mapping variation is wired into this legacy "
"integrator."
);
}
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 *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 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 &gravity_gradient_dofs = *elfun[gravity_gradient_block];
MFEM_VERIFY(
density_dofs.Size() == density_dofs_count,
"GravityForceIntegrator received an incorrectly sized density "
density_dofs.Size() == density_dofs_count, "GravityForceIntegrator received an incorrectly sized density "
"state."
);
MFEM_VERIFY(
@@ -254,8 +228,7 @@ namespace mean_field::integrators {
);
mfem::DenseMatrix *dv_drho = elmats(velocity_block, density_block);
mfem::DenseMatrix *dv_dgrad_phi =
m_jacobian_mode == GravityForceJacobianMode::field_coupled
mfem::DenseMatrix *dv_dgrad_phi = m_jacobian_mode == GravityForceJacobianMode::field_coupled
? elmats(velocity_block, gravity_gradient_block)
: nullptr;
@@ -265,9 +238,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 +247,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_mapping.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 +270,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 +280,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;
}
}
}

View File

@@ -5,9 +5,15 @@ module mean_field;
namespace mean_field::integrators {
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(
const mapping::DomainMapper &map
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_map(map) { };
: m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
void ContinuityVolumeIntegrator::AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -15,6 +21,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
@@ -29,8 +37,7 @@ 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;
void *data_rho_before = elvec[1] ? (void *)elvec[1]->GetData() : nullptr;
int size_rho_before = elvec[1] ? elvec[1]->Size() : -1;
if (elvec[0]) {
@@ -42,17 +49,15 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
@@ -88,6 +93,7 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
@@ -113,17 +119,15 @@ 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);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcShape(ip, shape_v);
fe_rho->CalcShape(ip, shape_rho);
@@ -149,8 +153,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,8 +166,7 @@ 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;
}
}
}
@@ -172,9 +174,15 @@ namespace mean_field::integrators {
}
ContinuityFaceIntegrator::ContinuityFaceIntegrator(
const mapping::DomainMapper &map
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
)
: m_map(map) {
: m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void ContinuityFaceIntegrator::AssembleFaceVector(
@@ -184,6 +192,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvect
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
@@ -208,9 +218,9 @@ namespace mean_field::integrators {
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) {
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return; // No flux contribution for vacuum faces
}
@@ -218,29 +228,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_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 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 +251,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_mapping.GetFaceQuadratureContext(Tr, face_ip);
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
@@ -303,6 +304,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v_minus = el1[0];
const mfem::FiniteElement *fe_v_plus = el2[0];
const mfem::FiniteElement *fe_rho_minus = el1[1];
@@ -317,8 +320,7 @@ namespace mean_field::integrators {
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;
@@ -342,13 +344,10 @@ namespace mean_field::integrators {
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 +355,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_mapping.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) {
@@ -390,8 +387,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 +395,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 +412,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,13 +423,12 @@ namespace mean_field::integrators {
}
}
bool ContinuityFaceIntegrator::skip_face(
const mfem::FaceElementTransformations &Tr
) {
constexpr int VACUUM_ATTR = 3;
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations &Tr) {
const int attr_minus = Tr.Elem1->Attribute;
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) {
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
if (DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_minus) ||
DomainSchema::template attribute_belongs_to<utils::domain::Vacuum>(attr_plus)) {
return true; // No flux contribution for vacuum faces
}
if (Tr.Elem2 == nullptr) {

View File

@@ -4,11 +4,17 @@ module mean_field;
namespace mean_field::integrators {
ViscosityIntegrator::ViscosityIntegrator(
const mapping::DomainMapper &map,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const double mu,
const int quad_boost
)
: m_map(map),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_mu(mu),
m_quad_boost(quad_boost) {
}
@@ -23,6 +29,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array<mfem::Vector *> &elvec
) {
m_mapping.InvalidateCache();
if (utils::is_vacuum(Tr, elvec)) {
return;
}
@@ -49,16 +57,14 @@ 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) {
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
Tr.SetIntPoint(&ip);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
@@ -104,6 +110,8 @@ namespace mean_field::integrators {
const mfem::Array<const mfem::Vector *> &elfun,
const mfem::Array2D<mfem::DenseMatrix *> &elmats
) {
m_mapping.InvalidateCache();
const mfem::FiniteElement *fe_v = el[0];
const mfem::FiniteElement *fe_rho = el[1];
@@ -127,13 +135,12 @@ 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);
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
fe_v->CalcDShape(ip, dshape_v_ref);
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
@@ -158,8 +165,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;
}
}

View File

@@ -9,11 +9,17 @@ namespace mean_field::mapping {
/// MappedScalarCoefficient ///
//////////////////////////////
MappedScalarCoefficient::MappedScalarCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Coefficient &coeff,
const COORDINATE_SPACE coord_space
)
: m_map(map),
: m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff),
m_coord_space(coord_space) { };
@@ -27,8 +33,12 @@ namespace mean_field::mapping {
switch (m_coord_space) {
case COORDINATE_SPACE::PHYSICAL: {
f_val = eval_at_point(m_coeff, T, ip);
const double detJ = m_map.ComputeDetJ(T, ip);
return f_val * fabs(detJ);
VolumeMappingContext context;
MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
"Mapped scalar coefficient encountered an invalid mapping."
);
return f_val * std::abs(context.mapping.mapping_determinant);
}
case COORDINATE_SPACE::REFERENCE: {
f_val = m_coeff.Eval(T, ip);
@@ -50,21 +60,33 @@ namespace mean_field::mapping {
//////////////////////////////////
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
mfem::Coefficient &sigma,
const int dim
)
: MatrixCoefficient(dim),
m_map(map),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(&sigma),
m_tensor(nullptr) { };
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
MatrixCoefficient &sigma
)
: MatrixCoefficient(sigma.GetHeight()),
m_map(map),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_scalar(nullptr),
m_tensor(&sigma) { };
@@ -76,10 +98,13 @@ namespace mean_field::mapping {
const int dim = height;
T.SetIntPoint(&ip);
mfem::DenseMatrix J(dim, dim), JInv(dim, dim);
m_map.ComputeJacobian(T, J);
const double detJ = J.Det();
mfem::CalcInverse(J, JInv);
VolumeMappingContext context;
MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
"Mapped diffusion coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
if (m_scalar) {
const double sig_val = m_scalar->Eval(T, ip);
@@ -101,11 +126,17 @@ namespace mean_field::mapping {
/// MappedVectorCoefficient ///
///////////////////////////////
MappedVectorCoefficient::MappedVectorCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
VectorCoefficient &coeff
)
: VectorCoefficient(coeff.GetVDim()),
m_map(map),
m_mapping(
mapper,
displacement,
compactification_coordinate
),
m_coeff(coeff) { };
void MappedVectorCoefficient::Eval(
@@ -116,9 +147,13 @@ namespace mean_field::mapping {
const int dim = vdim;
T.SetIntPoint(&ip);
mfem::DenseMatrix JInv(dim, dim);
m_map.ComputeInverseJacobian(T, JInv);
double detJ = m_map.ComputeDetJ(T, ip);
VolumeMappingContext context;
MFEM_VERIFY(
m_mapping.EvaluateVolume(T, ip, context) == MappingStatus::valid,
"Mapped vector coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &JInv = context.mapping.inverse_mapping_jacobian;
const double detJ = context.mapping.mapping_determinant;
mfem::Vector C_phys(dim);
m_coeff.Eval(C_phys, T, ip);
@@ -132,28 +167,43 @@ namespace mean_field::mapping {
/// PhysicalPositionFunctionCoefficient ///
///////////////////////////////////////////
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Func f // std::function<double(const mfem::Vector&)>
)
: m_f(std::move(f)),
m_map(map) { };
m_mapping(
mapper,
displacement,
compactification_coordinate
) { };
double PhysicalPositionFunctionCoefficient::Eval(
mfem::ElementTransformation &T,
const mfem::IntegrationPoint &ip
) {
T.SetIntPoint(&ip);
mfem::Vector x;
m_map.GetPhysicalPoint(T, ip, x);
return m_f(x);
MappingPointContext context;
MFEM_VERIFY(
m_mapping.EvaluatePoint(T, ip, context) == MappingStatus::valid,
"Physical-position coefficient encountered an invalid mapping."
);
return m_f(context.physical_position);
}
MappedHDivMassCoefficient::MappedHDivMassCoefficient(
const DomainMapper &map,
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const int dim
)
: MatrixCoefficient(dim),
m_map(map) {
m_mapping(
mapper,
displacement,
compactification_coordinate
) {
}
void MappedHDivMassCoefficient::Eval(
@@ -163,15 +213,15 @@ namespace mean_field::mapping {
) {
transformation.SetIntPoint(&integration_point);
mfem::DenseMatrix map_jacobian(height, height);
m_map.ComputeJacobian(transformation, map_jacobian);
const double map_determinant = map_jacobian.Det();
VolumeMappingContext context;
MFEM_VERIFY(
map_determinant > 0.0,
"Domain mapping has a non-positive Jacobian determinant."
m_mapping.EvaluateVolume(transformation, integration_point, context) == MappingStatus::valid,
"Mapped H(div) coefficient encountered an invalid mapping."
);
const mfem::DenseMatrix &map_jacobian = context.mapping.mapping_jacobian;
const double map_determinant = context.mapping.mapping_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);

View File

@@ -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;
}
@@ -79,11 +69,9 @@ namespace mean_field::mapping::compactification {
}
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 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;
}
@@ -95,9 +83,7 @@ namespace mean_field::mapping::compactification {
}
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_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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,916 +0,0 @@
module;
#include <cmath>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
#include <utility>
module mean_field;
import :mapping.types;
import :mapping.compactification;
import :utils.user;
namespace {
bool vector_is_finite(const mfem::Vector &vector) {
for (int i = 0; i < vector.Size(); ++i) {
if (!std::isfinite(vector(i)))
return false;
}
return true;
}
bool matrix_is_finite(const mfem::DenseMatrix &matrix) {
for (int i = 0; i < matrix.Height(); ++i) {
for (int j = 0; j < matrix.Width(); ++j) {
if (!std::isfinite(matrix(i, j)))
return false;
}
}
return true;
}
} // namespace
namespace mean_field::mapping {
ElementCompactificationData::ElementCompactificationData(
const mfem::FiniteElement &element,
const mfem::Vector &dofs
)
: m_element(&element),
m_dofs(dofs) {
if (element.GetRangeType() != mfem::FiniteElement::SCALAR) {
throw std::invalid_argument(
"Compactification coordinate requires a scalar finite element."
);
}
if (element.GetMapType() != mfem::FiniteElement::VALUE) {
throw std::invalid_argument(
"Compactification coordinate requires a value-mapped scalar "
"finite "
"element."
);
}
if (element.GetDerivType() != mfem::FiniteElement::GRAD) {
throw std::invalid_argument(
"Compactification coordinate finite element must provide a "
"gradient."
);
}
if (element.GetDof() <= 0) {
throw std::invalid_argument(
"Compactification coordinate finite element has no degrees of "
"freedom."
);
}
if (dofs.Size() != element.GetDof()) {
throw std::invalid_argument(
"Compactification coordinate DOF count does not match its "
"finite "
"element."
);
}
}
const mfem::FiniteElement &
ElementCompactificationData::GetElement() const noexcept {
return *m_element;
}
const mfem::Vector &ElementCompactificationData::GetDofs() const noexcept {
return m_dofs;
}
int ElementCompactificationData::GetDofCount() const noexcept {
return m_dofs.Size();
}
ElementDisplacementData::ElementDisplacementData(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs,
const mfem::Ordering::Type ordering
)
: m_element(&element),
m_dimension(0),
m_ordering(ordering) {
const int dof_count = element.GetDof();
if (dof_count <= 0)
throw std::invalid_argument(
"The displacement element must have at least one degree of "
"freedom."
);
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 "
"element degree-of-freedom count."
);
}
m_dimension = displacement_dofs.Size() / dof_count;
m_dof_matrix.SetSize(dof_count, m_dimension);
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);
}
}
} 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);
}
}
} else {
throw std::invalid_argument(
"Unsupported MFEM displacement ordering."
);
}
}
const mfem::FiniteElement &
ElementDisplacementData::GetElement() const noexcept {
return *m_element;
}
const mfem::DenseMatrix &
ElementDisplacementData::GetDofMatrix() const noexcept {
return m_dof_matrix;
}
int ElementDisplacementData::GetDimension() const noexcept {
return m_dimension;
}
int ElementDisplacementData::GetDofCount() const noexcept {
return m_element->GetDof();
}
mfem::Ordering::Type ElementDisplacementData::GetOrdering() const noexcept {
return m_ordering;
}
ElementDisplacementData ElementDisplacementDataFromElementVDofs(
const mfem::FiniteElement &element,
const mfem::Vector &displacement_dofs
) {
return ElementDisplacementData(
element, displacement_dofs, mfem::Ordering::byNODES
);
}
DomainMapperStateless::Workspace::Workspace(const int dimension) {
SetDimension(dimension);
}
void DomainMapperStateless::Workspace::SetDimension(const int dimension) {
if (dimension <= 0) {
throw std::invalid_argument(
"Domain mapping workspace dimension must be positive."
);
}
m_dimension = dimension;
m_field_value.SetSize(dimension);
m_field_jacobian.SetSize(dimension, dimension);
m_compactification_point.coordinate = 0.0;
m_compactification_point.coordinate_gradient.SetSize(dimension);
m_reference_normal.SetSize(dimension);
m_mapped_normal.SetSize(dimension);
m_full_element_jacobian.SetSize(dimension, dimension);
m_vector_temp.SetSize(dimension);
m_matrix_temp_1.SetSize(dimension, dimension);
m_matrix_temp_2.SetSize(dimension, dimension);
m_exterior_result.physical_position.SetSize(dimension);
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
);
}
int DomainMapperStateless::Workspace::GetDimension() const noexcept {
return m_dimension;
}
DomainMapperStateless::DomainMapperStateless(
const utils::DomainMapperStatelessOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
)
: 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."
);
if (m_options.vacuum_element_attribute <= 0)
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."
);
}
bool DomainMapperStateless::IsCompactifiedElement(
const mfem::ElementTransformation &transformation
) const noexcept {
return transformation.Attribute == m_options.vacuum_element_attribute;
}
int DomainMapperStateless::GetDimension() const noexcept {
return m_options.dimension;
}
int DomainMapperStateless::GetVacuumElementAttribute() const noexcept {
return m_options.vacuum_element_attribute;
}
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;
if (displacement.GetDimension() != m_options.dimension) {
throw std::invalid_argument(
"Displacement field dimension does not match the domain mapper "
"dimension."
);
}
if (displacement.GetElement().GetDim() != m_options.dimension) {
throw std::invalid_argument(
"Displacement finite element dimension does not match the "
"domain "
"mapper dimension."
);
}
if (compactification.GetElement().GetDim() != m_options.dimension) {
throw std::invalid_argument(
"Compactification finite element dimension does not match the "
"domain "
"mapper dimension."
);
}
if (displacement.GetElement().GetGeomType() !=
compactification.GetElement().GetGeomType()) {
throw std::invalid_argument(
"Displacement and compactification finite elements have "
"different "
"geometries."
);
}
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) {
throw std::invalid_argument(
"Compactification coordinate requires a value-mapped finite "
"element."
);
}
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()) {
throw std::invalid_argument(
"Compactification coordinate DOF count does not match its "
"finite "
"element."
);
}
}
MappingStatus DomainMapperStateless::EvaluateCompactificationCoordinate(
const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
CompactificationPointData &point_data
) const {
const mfem::FiniteElement &element = compactification.GetElement();
const mfem::Vector &dofs = compactification.GetDofs();
const int dof_count = element.GetDof();
if (workspace.GetDimension() != m_options.dimension ||
transformation.GetSpaceDim() != m_options.dimension ||
element.GetDim() != m_options.dimension) {
return MappingStatus::invalid_dimension;
}
if (dofs.Size() != dof_count) {
return MappingStatus::invalid_dimension;
}
for (int i = 0; i < dofs.Size(); ++i) {
if (!std::isfinite(dofs(i)))
return MappingStatus::non_finite_input;
}
transformation.SetIntPoint(&integration_point);
workspace.m_compactification_shape.SetSize(dof_count);
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
);
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
);
if (!std::isfinite(point_data.coordinate)) {
return MappingStatus::non_finite_result;
}
for (int d = 0; d < point_data.coordinate_gradient.Size(); ++d) {
if (!std::isfinite(point_data.coordinate_gradient(d)))
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
void DomainMapperStateless::EvaluateField(
const ElementDisplacementData &field,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian
) const {
transformation.SetIntPoint(&integration_point);
const mfem::FiniteElement &element = field.GetElement();
const mfem::DenseMatrix &dof_matrix = field.GetDofMatrix();
workspace.m_shape.SetSize(element.GetDof());
workspace.m_mesh_dshape.SetSize(element.GetDof(), m_options.dimension);
element.CalcShape(integration_point, workspace.m_shape);
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
value.SetSize(m_options.dimension);
dof_matrix.MultTranspose(workspace.m_shape, value);
jacobian.SetSize(m_options.dimension, m_options.dimension);
mfem::MultAtB(dof_matrix, workspace.m_mesh_dshape, jacobian);
}
MappingStatus DomainMapperStateless::EvaluatePoint(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
MappingPointContext &context
) const {
ValidateElementData(element_data);
if (workspace.GetDimension() != m_options.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 geometry does not match the "
"supplied "
"element data."
);
transformation.SetIntPoint(&integration_point);
context.reference_position.SetSize(m_options.dimension);
transformation.Transform(integration_point, context.reference_position);
EvaluateField(
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) ||
!matrix_is_finite(workspace.m_field_jacobian)) {
return MappingStatus::non_finite_input;
}
context.displaced_position.SetSize(m_options.dimension);
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 = workspace.m_field_jacobian;
for (int i = 0; i < m_options.dimension; ++i)
context.displacement_jacobian(i, i) += 1.0;
context.compactified = IsCompactifiedElement(transformation);
if (context.compactified) {
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
};
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;
} 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))
return MappingStatus::non_finite_result;
context.mapping_determinant = context.mapping_jacobian.Det();
if (!std::isfinite(context.mapping_determinant))
return MappingStatus::non_finite_result;
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
);
if (!matrix_is_finite(context.inverse_mapping_jacobian))
return MappingStatus::non_finite_result;
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateVolume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
VolumeMappingContext &context
) const {
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
);
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;
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;
return MappingStatus::valid;
}
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."
);
return *transformation.Elem1;
}
MFEM_VERIFY(
transformation.Elem2 != nullptr,
"The face does not have an element-2 transformation."
);
return *transformation.Elem2;
}
const mfem::IntegrationPoint &
DomainMapperStateless::SelectFaceElementIntegrationPoint(
mfem::FaceElementTransformations &transformation,
const FaceElementSide side
) {
mfem::ElementTransformation &element_transformation =
SelectFaceElementTransformation(transformation, side);
return element_transformation.GetIntPoint();
}
MappingStatus DomainMapperStateless::EvaluateFace(
const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation,
const FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
FaceMappingContext &context
) const {
transformation.SetAllIntPoints(&integration_point);
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
);
if (point_status != MappingStatus::valid)
return point_status;
workspace.m_reference_normal.SetSize(m_options.dimension);
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)
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
);
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)
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.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)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluatePointVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const MappingPointContext &base_context,
Workspace &workspace,
MappingPointVariation &variation
) const {
ValidateElementData(element_data);
const ElementMappingData direction_data{
.displacement = direction,
.compactification = element_data.compactification
};
ValidateElementData(direction_data);
if (element_data.displacement.GetDofCount() != direction.GetDofCount())
throw std::invalid_argument(
"The displacement and direction elements have different "
"degree-of-freedom counts."
);
if (workspace.GetDimension() != m_options.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 "
"domain."
);
EvaluateField(
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))
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
);
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
};
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
};
// ReSharper disable once CppTooWideScopeInitStatement
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;
} else {
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
);
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.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
);
variation.inverse_mapping_jacobian_variation *= -1.0;
if (!vector_is_finite(variation.physical_position_variation) ||
!matrix_is_finite(variation.mapping_jacobian_variation) ||
!matrix_is_finite(variation.inverse_mapping_jacobian_variation) ||
!std::isfinite(variation.mapping_determinant_variation)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateVolumeVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
const VolumeMappingContext &base_context,
Workspace &workspace,
VolumeMappingVariation &variation
) const {
const MappingStatus point_status = EvaluatePointVariation(
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.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
);
variation.inverse_element_jacobian_variation *= -1.0;
variation.weight_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))
return MappingStatus::non_finite_result;
return MappingStatus::valid;
}
MappingStatus DomainMapperStateless::EvaluateFaceVariation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation,
const FaceElementSide side,
const mfem::IntegrationPoint &integration_point,
const FaceMappingContext &base_context,
Workspace &workspace,
FaceMappingVariation &variation
) const {
transformation.SetAllIntPoints(&integration_point);
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,
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
);
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)
return MappingStatus::non_finite_result;
base_context.mapping.inverse_mapping_jacobian.MultTranspose(
workspace.m_reference_normal, workspace.m_vector_temp
);
workspace.m_mapped_normal = workspace.m_vector_temp;
workspace.m_mapped_normal *= base_context.mapping.mapping_determinant;
variation.physical_normal_variation.SetSize(m_options.dimension);
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.Add(
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)
return MappingStatus::non_finite_result;
const double mapped_normal_magnitude_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 /= 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;
if (!vector_is_finite(variation.physical_normal_variation) ||
!std::isfinite(variation.physical_surface_weight_variation) ||
!std::isfinite(variation.normal_flux_scale_variation)) {
return MappingStatus::non_finite_result;
}
return MappingStatus::valid;
}
} // namespace mean_field::mapping

View File

@@ -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(
@@ -240,37 +207,32 @@ namespace mean_field::mapping {
mfem::DenseMatrix &mass_tensor_variation
) {
const double determinant = context.mapping_determinant;
const double determinant_variation =
variation.mapping_determinant_variation;
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;
const mfem::DenseMatrix &jacobian = context.mapping_jacobian;
const mfem::DenseMatrix &jacobianVariation = variation.mapping_jacobian_variation;
const double inverseDeterminant = 1.0 / determinant;
const double determinantScale = determinant_variation * inverseDeterminant;
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
);
mass_tensor_variation += right_jacobian_variation;
mass_tensor_variation *= 1 / determinant;
mass_tensor_variation -= determinant_correction;
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
double gram{0.0};
double gramVariation{0.0};
for (int inner = 0; inner < dimension; ++inner) {
gram += jacobian(inner, row) * jacobian(inner, column);
gramVariation += jacobian(inner, row) * jacobianVariation(inner, column) +
jacobianVariation(inner, row) * jacobian(inner, column);
}
mass_tensor_variation(row, column) = inverseDeterminant * (gramVariation - determinantScale * gram);
}
}
}
} // namespace mean_field::mapping

View File

@@ -0,0 +1,67 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
#include <utility>
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::Polytrope &PolytropicStructure::equationOfState() const noexcept {
return m_equationOfState;
}
double PolytropicStructure::targetMass() const noexcept {
return m_targetMass;
}
StructureSeed PolytropicStructure::makeInitialSeed(const StructureSeedRequest &request) const {
const seed::RadialProfile profile = seed::generateLaneEmdenProfile(
m_equationOfState, dimensions::DensityValue{request.centralDensity}, request.radialSampleCount
);
return {
.radius = profile.radius,
.density = profile.density,
.enthalpy = profile.specificEnthalpy,
.stellarRadius = profile.stellarRadius.value(),
.centralDensity = profile.centralDensity.value(),
.centralEnthalpy = profile.centralSpecificEnthalpy.value()
};
}
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
)
);
}
}
} // namespace mean_field::models::structure

View File

@@ -1,135 +1,190 @@
module;
#include <cstdint>
#include <cmath>
#include <mfem.hpp>
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 <typename Stamp>
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(
BarotropicClosureLinearizationContext::BarotropicClosureLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope
const mapping::DomainMapper &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) {
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."
);
if (m_isPrepared && revisions == m_revisions) {
return;
}
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;
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;
++m_preparationCount;
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

View File

@@ -7,26 +7,172 @@ module mean_field;
import :operators.context.gravity_field;
namespace {
void validate_displacement(
const mean_field::fem::FEM &f,
const mfem::Vector &displacement_true
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
void true_to_local(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &true_vector,
mfem::Vector &local_vector
) {
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldGeometryContext requires the "
"displacement finite-element space."
true_vector.Size() == finite_element_space.GetTrueVSize(),
"True-DOF operator received an input vector with the wrong size."
);
local_vector.SetSize(finite_element_space.GetVSize());
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(true_vector, local_vector);
} else {
local_vector = true_vector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector
) {
MFEM_VERIFY(
local_vector.Size() == finite_element_space.GetVSize(),
"True-DOF operator produced a local vector with the wrong size."
);
true_vector.SetSize(finite_element_space.GetTrueVSize());
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
true_vector = local_vector;
}
}
bool communicator_has_single_rank(const MPI_Comm communicator) {
int size = 0;
MFEM_VERIFY(MPI_Comm_size(communicator, &size) == MPI_SUCCESS, "Failed to query the MPI communicator size.");
MFEM_VERIFY(size > 0, "The MPI communicator must contain at least one rank.");
return size == 1;
}
class TrueDofParMixedBilinearFormOperator final : public mfem::Operator {
public:
TrueDofParMixedBilinearFormOperator(
const mfem::ParFiniteElementSpace &trial_space,
const mfem::ParFiniteElementSpace &test_space,
std::unique_ptr<mfem::ParMixedBilinearForm> local_form
)
: Operator(
test_space.GetTrueVSize(),
trial_space.GetTrueVSize()
),
m_trial_space(trial_space),
m_test_space(test_space),
m_local_form(std::move(local_form)),
m_single_rank(communicator_has_single_rank(trial_space.GetComm())) {
int communicators_compare = MPI_UNEQUAL;
MFEM_VERIFY(
MPI_Comm_compare(trial_space.GetComm(), test_space.GetComm(), &communicators_compare) == MPI_SUCCESS,
"Failed to compare mixed-operator MPI communicators."
);
MFEM_VERIFY(
displacement_true.Size() == f.displacementFes->GetTrueVSize(),
communicators_compare == MPI_IDENT || communicators_compare == MPI_CONGRUENT,
"True-DOF mixed operator requires congruent trial and test communicators."
);
MFEM_VERIFY(m_local_form != nullptr, "True-DOF mixed operator requires a local bilinear form.");
MFEM_VERIFY(
m_local_form->Width() == m_trial_space.GetVSize(),
"True-DOF mixed operator received an incompatible trial space."
);
MFEM_VERIFY(
m_local_form->Height() == m_test_space.GetVSize(),
"True-DOF mixed operator received an incompatible test space."
);
}
void Mult(
const mfem::Vector &input,
mfem::Vector &output
) const override {
MFEM_VERIFY(input.Size() == Width(), "True-DOF mixed operator received an input with the wrong size.");
if (m_single_rank) [[likely]] {
output.SetSize(Height());
m_local_form->Mult(input, output);
return;
}
true_to_local(m_trial_space, input, m_trial_local);
m_test_local.SetSize(m_test_space.GetVSize());
m_local_form->Mult(m_trial_local, m_test_local);
local_to_true(m_test_space, m_test_local, output);
}
void MultTranspose(
const mfem::Vector &input,
mfem::Vector &output
) const override {
MFEM_VERIFY(input.Size() == Height(), "True-DOF mixed transpose received an input with the wrong size.");
if (m_single_rank) [[likely]] {
output.SetSize(Width());
m_local_form->MultTranspose(input, output);
return;
}
true_to_local(m_test_space, input, m_test_local);
m_trial_local.SetSize(m_trial_space.GetVSize());
m_local_form->MultTranspose(m_test_local, m_trial_local);
local_to_true(m_trial_space, m_trial_local, output);
}
private:
const mfem::ParFiniteElementSpace &m_trial_space;
const mfem::ParFiniteElementSpace &m_test_space;
std::unique_ptr<mfem::ParMixedBilinearForm> m_local_form;
mutable mfem::Vector m_trial_local;
mutable mfem::Vector m_test_local;
bool m_single_rank;
};
[[nodiscard]] std::unique_ptr<mfem::Operator> make_divergence_operator(const mean_field::fem::FEM &f) {
auto divergence =
std::make_unique<mfem::ParMixedBilinearForm>(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
divergence->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &trialElement = *f.gravityFluxFes->GetTypicalFE();
const mfem::FiniteElement &testElement = *f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(0);
f.quadratureFactory->configure_gravity_divergence(
*integrator, mean_field::quadrature::QuadratureRole::discretization, trialElement, testElement,
transformation, mean_field::utils::DOMAINS::ALL, mean_field::quadrature::MappingKind::none
);
divergence->AddDomainIntegrator(integrator.release());
divergence->Assemble();
return std::make_unique<TrueDofParMixedBilinearFormOperator>(
*f.gravityFluxFes, *f.gravityPotentialFes, std::move(divergence)
);
}
void validate_displacement(
const mean_field::field::FieldDofMap &displacement_map,
const mfem::Vector &displacement
) {
MFEM_VERIFY(
displacement.Size() == displacement_map.reduced_size(),
"GravityFieldGeometryContext received a displacement vector with "
"the "
"wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"GravityFieldGeometryContext received a non-finite "
std::isfinite(displacement(i)), "GravityFieldGeometryContext received a non-finite "
"displacement "
"value."
);
@@ -34,52 +180,32 @@ namespace {
}
void validate_linearization_state(
const mean_field::fem::FEM &f,
const mean_field::operators::context::gravity_field::
GravityFieldStateView &state
const mean_field::field::FieldDofMap &density_map,
const mean_field::field::FieldDofMap &displacement_map,
const mean_field::field::FieldDofMap &gravity_gradient_map,
const mean_field::field::FieldDofMap &gravity_potential_map,
const mean_field::operators::context::gravity_field::GravityFieldStateView &state
) {
MFEM_VERIFY(
f.densityFes != nullptr, "GravityFieldLinearizationContext "
"requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldLinearizationContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldLinearizationContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldLinearizationContext requires "
"the displacement finite-element space."
);
MFEM_VERIFY(
state.density.Size() == f.densityFes->GetTrueVSize(),
state.density.Size() == density_map.reduced_size(),
"GravityFieldLinearizationContext received a density vector with "
"the "
"wrong size."
);
MFEM_VERIFY(
state.displacement.Size() == f.displacementFes->GetTrueVSize(),
state.displacement.Size() == displacement_map.reduced_size(),
"GravityFieldLinearizationContext received a displacement vector "
"with "
"the wrong size."
);
MFEM_VERIFY(
state.gravity_gradient.Size() == f.gravityFluxFes->GetTrueVSize(),
state.gravity_gradient.Size() == gravity_gradient_map.reduced_size(),
"GravityFieldLinearizationContext received a gravity-gradient "
"vector "
"with the wrong size."
);
MFEM_VERIFY(
state.gravity_potential.Size() ==
f.gravityPotentialFes->GetTrueVSize(),
state.gravity_potential.Size() == gravity_potential_map.reduced_size(),
"GravityFieldLinearizationContext received a gravity-potential "
"vector "
"with the wrong size."
@@ -87,8 +213,7 @@ namespace {
for (int i = 0; i < state.density.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.density(i)),
"GravityFieldLinearizationContext received a non-finite "
std::isfinite(state.density(i)), "GravityFieldLinearizationContext received a non-finite "
"density "
"value."
);
@@ -96,8 +221,7 @@ namespace {
for (int i = 0; i < state.displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.displacement(i)),
"GravityFieldLinearizationContext received a non-finite "
std::isfinite(state.displacement(i)), "GravityFieldLinearizationContext received a non-finite "
"displacement "
"value."
);
@@ -105,16 +229,14 @@ namespace {
for (int i = 0; i < state.gravity_gradient.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(state.gravity_gradient(i)),
"GravityFieldLinearizationContext received a non-finite "
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 "
std::isfinite(state.gravity_potential(i)), "GravityFieldLinearizationContext received a non-finite "
"gravity-potential value."
);
}
@@ -124,46 +246,42 @@ namespace {
namespace mean_field::operators::context::gravity_field {
GravityFieldGeometryContext::GravityFieldGeometryContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
const mapping::DomainMapper &domain_mapper
)
: m_fem(f),
m_domain_mapper(domain_mapper) {
m_domain_mapper(domain_mapper),
m_displacement_map(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh.");
MFEM_VERIFY(
f.mesh != nullptr, "GravityFieldGeometryContext requires a mesh."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldGeometryContext requires the "
f.gravityFluxFes != nullptr, "GravityFieldGeometryContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"GravityFieldGeometryContext requires the density finite-element "
f.densityFes != nullptr, "GravityFieldGeometryContext requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldGeometryContext requires the gravity-potential "
f.gravityPotentialFes != nullptr, "GravityFieldGeometryContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldGeometryContext requires the "
f.displacementFes != nullptr, "GravityFieldGeometryContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"GravityFieldGeometryContext requires the compactification "
f.compactificationFes != nullptr, "GravityFieldGeometryContext requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"GravityFieldGeometryContext requires the compactification "
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(),
@@ -173,11 +291,30 @@ namespace mean_field::operators::context::gravity_field {
}
GravityFieldGeometryPreparation GravityFieldGeometryContext::Prepare(
const mfem::Vector &displacement_true,
const mfem::Vector &displacement,
const DiscretizationRevision discretization_revision,
const DisplacementRevision displacement_revision
) {
validate_displacement(m_fem, displacement_true);
return PrepareImpl(
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
);
}
GravityFieldGeometryPreparation GravityFieldGeometryContext::PreparePrimal(
const mfem::Vector &displacement,
const DiscretizationRevision discretization_revision,
const DisplacementRevision displacement_revision
) {
return PrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
}
GravityFieldGeometryPreparation GravityFieldGeometryContext::PrepareImpl(
const mfem::Vector &displacement,
const DiscretizationRevision discretization_revision,
const DisplacementRevision displacement_revision,
const PreparationMode mode
) {
validate_displacement(m_displacement_map, displacement);
if (m_is_prepared) {
MFEM_VERIFY(
@@ -192,84 +329,91 @@ 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;
const bool requires_variation = mode == PreparationMode::linearization;
const bool variation_upgrade = requires_variation && !m_variation_state_prepared;
GravityFieldGeometryPreparation preparation;
if (!discretization_changed && !displacement_changed) {
if (!discretization_changed && !displacement_changed && !variation_upgrade) {
return preparation;
}
if (discretization_changed) {
auto mass_operator =
std::make_unique<PreparedMappedHDivMassOperator>(
m_fem, m_domain_mapper
);
auto source_operator =
std::make_unique<PreparedMappedGravitySourceOperator>(
m_fem, m_domain_mapper
);
const auto prepare_mass = [&](PreparedMappedHDivMassOperator &mass_operator) {
if (requires_variation) {
mass_operator.Prepare(displacement);
} else {
mass_operator.PreparePrimal(displacement);
}
};
const auto prepare_source = [&](PreparedMappedGravitySourceOperator &source_operator) {
if (requires_variation) {
source_operator.Prepare(displacement);
} else {
source_operator.PreparePrimal(displacement);
}
};
mass_operator->Prepare(displacement_true);
source_operator->Prepare(displacement_true);
if (discretization_changed) {
auto mass_operator = std::make_unique<PreparedMappedHDivMassOperator>(m_fem, m_domain_mapper);
auto source_operator = std::make_unique<PreparedMappedGravitySourceOperator>(m_fem, m_domain_mapper);
auto divergence_operator = make_divergence_operator(m_fem);
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
prepare_mass(*mass_operator);
prepare_source(*source_operator);
m_mass_operator = std::move(mass_operator);
m_source_operator = std::move(source_operator);
m_divergence_operator = std::move(divergence_operator);
m_transpose_divergence_operator = std::move(transpose_divergence_operator);
preparation.reconstructed_operators = true;
preparation.rebuilt_mass_operator = true;
preparation.rebuilt_source_operator = true;
preparation.rebuilt_divergence_operator = true;
} else {
MFEM_VERIFY(
m_mass_operator != nullptr, "GravityFieldGeometryContext has "
"no prepared H(div) mass operator."
);
MFEM_VERIFY(
m_source_operator != nullptr,
"GravityFieldGeometryContext has no prepared gravity source "
m_source_operator != nullptr, "GravityFieldGeometryContext has no prepared gravity source "
"operator."
);
m_mass_operator->Prepare(displacement_true);
m_source_operator->Prepare(displacement_true);
prepare_mass(*m_mass_operator);
prepare_source(*m_source_operator);
preparation.rebuilt_mass_operator = true;
preparation.rebuilt_source_operator = true;
}
m_displacement_true = displacement_true;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
m_discretization_revision = discretization_revision;
m_displacement_revision = displacement_revision;
m_is_prepared = true;
m_variation_state_prepared = requires_variation;
preparation.refreshed_variation_state = true;
preparation.refreshed_variation_state = requires_variation;
return preparation;
}
const PreparedMappedHDivMassOperator &
GravityFieldGeometryContext::GetMassOperator() const {
const PreparedMappedHDivMassOperator &GravityFieldGeometryContext::GetMassOperator() const {
MFEM_VERIFY(
m_is_prepared,
"GravityFieldGeometryContext must be prepared before "
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."
);
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 "
m_is_prepared, "GravityFieldGeometryContext must be prepared before "
"accessing its source operator."
);
MFEM_VERIFY(
@@ -279,22 +423,40 @@ namespace mean_field::operators::context::gravity_field {
return *m_source_operator;
}
const mfem::Vector &GravityFieldGeometryContext::GetDisplacement() const {
const mfem::Operator &GravityFieldGeometryContext::GetDivergenceOperator() const {
MFEM_VERIFY(m_is_prepared, "GravityFieldGeometryContext must be prepared before accessing divergence.");
MFEM_VERIFY(m_divergence_operator != nullptr, "GravityFieldGeometryContext has no divergence operator.");
return *m_divergence_operator;
}
const mfem::Operator &GravityFieldGeometryContext::GetTransposeDivergenceOperator() const {
MFEM_VERIFY(
m_is_prepared,
"GravityFieldGeometryContext must be prepared before "
m_is_prepared, "GravityFieldGeometryContext must be prepared before accessing transpose divergence."
);
MFEM_VERIFY(
m_transpose_divergence_operator != nullptr,
"GravityFieldGeometryContext has no transpose-divergence operator."
);
return *m_transpose_divergence_operator;
}
const mfem::Vector &GravityFieldGeometryContext::GetDisplacementTrue() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldGeometryContext must be prepared before "
"accessing its displacement."
);
return m_displacement_true;
}
DiscretizationRevision
GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept {
const field::FieldDofMap &GravityFieldGeometryContext::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
DiscretizationRevision GravityFieldGeometryContext::GetDiscretizationRevision() const noexcept {
return m_discretization_revision;
}
DisplacementRevision
GravityFieldGeometryContext::GetDisplacementRevision() const noexcept {
DisplacementRevision GravityFieldGeometryContext::GetDisplacementRevision() const noexcept {
return m_displacement_revision;
}
@@ -304,12 +466,27 @@ namespace mean_field::operators::context::gravity_field {
GravityFieldLinearizationContext::GravityFieldLinearizationContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
const mapping::DomainMapper &domain_mapper
)
: m_fem(f),
m_geometry_context(
f,
domain_mapper
),
m_density_map(
field::make_field_dof_map<
field::Density,
DomainSchema>(*f.densityFes)
),
m_gravity_gradient_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityFluxFes)
),
m_gravity_potential_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
) {
MFEM_VERIFY(
f.densityFes != nullptr, "GravityFieldLinearizationContext "
@@ -317,18 +494,15 @@ namespace mean_field::operators::context::gravity_field {
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldLinearizationContext requires the gravity-potential "
f.gravityPotentialFes != nullptr, "GravityFieldLinearizationContext requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldLinearizationContext requires the "
f.gravityFluxFes != nullptr, "GravityFieldLinearizationContext requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldLinearizationContext requires "
f.displacementFes != nullptr, "GravityFieldLinearizationContext requires "
"the displacement finite-element space."
);
}
@@ -337,7 +511,10 @@ namespace mean_field::operators::context::gravity_field {
const GravityFieldStateView &state,
const GravityFieldRevisions &revisions
) {
validate_linearization_state(m_fem, state);
validate_linearization_state(
m_density_map, m_geometry_context.GetDisplacementMap(), m_gravity_gradient_map, m_gravity_potential_map,
state
);
if (m_is_prepared) {
MFEM_VERIFY(
@@ -353,8 +530,7 @@ namespace mean_field::operators::context::gravity_field {
"revision."
);
MFEM_VERIFY(
revisions.density >= m_revisions.density,
"GravityFieldLinearizationContext received an older density "
revisions.density >= m_revisions.density, "GravityFieldLinearizationContext received an older density "
"revision."
);
MFEM_VERIFY(
@@ -370,28 +546,26 @@ 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;
m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(state.density, m_density_true);
report.updated_density = true;
}
if (gravity_gradient_changed) {
m_gravity_gradient_true = state.gravity_gradient;
m_gravity_gradient_true.SetSize(m_gravity_gradient_map.full_size());
m_gravity_gradient_map.scatter(state.gravity_gradient, m_gravity_gradient_true);
report.updated_gravity_gradient = true;
}
@@ -401,8 +575,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."
@@ -410,7 +583,7 @@ namespace mean_field::operators::context::gravity_field {
return m_geometry_context;
}
const mfem::Vector &GravityFieldLinearizationContext::GetDensity() const {
const mfem::Vector &GravityFieldLinearizationContext::GetDensityTrue() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its density."
@@ -418,8 +591,7 @@ namespace mean_field::operators::context::gravity_field {
return m_density_true;
}
const mfem::Vector &
GravityFieldLinearizationContext::GetGravityGradient() const {
const mfem::Vector &GravityFieldLinearizationContext::GetGravityGradientTrue() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its gravity gradient."
@@ -427,8 +599,23 @@ namespace mean_field::operators::context::gravity_field {
return m_gravity_gradient_true;
}
const GravityFieldRevisions &
GravityFieldLinearizationContext::GetRevisions() const {
const field::FieldDofMap &GravityFieldLinearizationContext::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &GravityFieldLinearizationContext::GetDisplacementMap() const noexcept {
return m_geometry_context.GetDisplacementMap();
}
const field::FieldDofMap &GravityFieldLinearizationContext::GetGravityGradientMap() const noexcept {
return m_gravity_gradient_map;
}
const field::FieldDofMap &GravityFieldLinearizationContext::GetGravityPotentialMap() const noexcept {
return m_gravity_potential_map;
}
const GravityFieldRevisions &GravityFieldLinearizationContext::GetRevisions() const {
MFEM_VERIFY(
m_is_prepared, "GravityFieldLinearizationContext must be prepared "
"before accessing its revisions."

View File

@@ -9,6 +9,8 @@ module mean_field;
import :operators.context.hydrostatic_equilibrium;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
void validate_finite_vector(
const mfem::Vector &vector,
const char *message
@@ -19,45 +21,24 @@ namespace {
}
void validate_state(
const mean_field::fem::FEM &f,
const mean_field::operators::context::hydrostatic::
HydrostaticEquilibriumStateView &state
const mean_field::field::FieldDofMap &enthalpyMap,
const mean_field::field::FieldDofMap &gravityPotentialMap,
const mean_field::field::FieldDofMap &displacementMap,
const mean_field::operators::context::hydrostatic::HydrostaticEquilibriumStateView &state
) {
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"HydrostaticEquilibriumContext requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
state.enthalpy.Size() == f.enthalpyFes->GetTrueVSize(),
"HydrostaticEquilibriumContext received an "
state.enthalpy.Size() == enthalpyMap.reduced_size(), "HydrostaticEquilibriumContext received a supported "
"enthalpy vector with the wrong size."
);
MFEM_VERIFY(
state.gravityPotential.Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"HydrostaticEquilibriumContext received a "
"gravity-potential vector with the wrong size."
state.gravityPotential.Size() == gravityPotentialMap.reduced_size(),
"HydrostaticEquilibriumContext received a supported 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() == displacementMap.reduced_size(),
"HydrostaticEquilibriumContext received a supported displacement vector with the wrong size."
);
validate_finite_vector(
@@ -76,8 +57,7 @@ namespace {
);
MFEM_VERIFY(
std::isfinite(state.bernoulliConstant),
"HydrostaticEquilibriumContext received a "
std::isfinite(state.bernoulliConstant), "HydrostaticEquilibriumContext received a "
"non-finite Bernoulli constant."
);
}
@@ -95,46 +75,58 @@ namespace {
namespace mean_field::operators::context::hydrostatic {
HydrostaticEquilibriumContext::HydrostaticEquilibriumContext(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper
const mapping::DomainMapper &domainMapper
)
: m_f(f),
m_domainMapper(domainMapper) {
MFEM_VERIFY(
m_f.mesh != nullptr,
"HydrostaticEquilibriumContext requires a mesh."
);
m_domainMapper(domainMapper),
m_enthalpyMap(
field::make_field_dof_map<
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
m_gravityPotentialMap(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
m_displacementMap(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(m_f.mesh != nullptr, "HydrostaticEquilibriumContext requires a mesh.");
MFEM_VERIFY(
m_f.enthalpyFes != nullptr,
"HydrostaticEquilibriumContext requires the "
m_f.enthalpyFes != nullptr, "HydrostaticEquilibriumContext requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
m_f.gravityPotentialFes != nullptr,
"HydrostaticEquilibriumContext requires the "
m_f.gravityPotentialFes != nullptr, "HydrostaticEquilibriumContext requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
m_f.displacementFes != nullptr,
"HydrostaticEquilibriumContext requires the "
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 "
m_domainMapper.GetDimension() == m_f.mesh->Dimension(), "The hydrostatic context's stateless "
"domain-mapper dimension does not match the mesh "
"dimension."
);
m_baseEnthalpyTrue.SetSize(m_enthalpyMap.full_size());
m_baseGravityPotentialTrue.SetSize(m_gravityPotentialMap.full_size());
m_displacementTrue.SetSize(m_displacementMap.full_size());
}
HydrostaticPreparationReport HydrostaticEquilibriumContext::Prepare(
const HydrostaticEquilibriumStateView &state,
const HydrostaticEquilibriumDependencies &dependencies
) {
validate_state(m_f, state);
validate_state(m_enthalpyMap, m_gravityPotentialMap, m_displacementMap, state);
if (m_isPrepared) {
validate_dependency_transition(
@@ -168,44 +160,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;
@@ -215,18 +195,18 @@ namespace mean_field::operators::context::hydrostatic {
report.preparedBaseState = baseStatePreparationRequired;
if (staticChanged || enthalpyChanged) {
m_baseEnthalpyTrue = state.enthalpy;
m_enthalpyMap.scatter(state.enthalpy, m_baseEnthalpyTrue);
report.updatedEnthalpy = true;
}
if (staticChanged || gravityPotentialChanged) {
m_baseGravityPotentialTrue = state.gravityPotential;
m_gravityPotentialMap.scatter(state.gravityPotential, m_baseGravityPotentialTrue);
report.updatedGravityPotential = true;
}
if (geometryPreparationRequired) {
m_displacementTrue = state.displacement;
m_displacementMap.scatter(state.displacement, m_displacementTrue);
report.updatedDisplacement = true;
}
@@ -267,31 +247,38 @@ 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 field::FieldDofMap &HydrostaticEquilibriumContext::GetEnthalpyMap() const noexcept {
return m_enthalpyMap;
}
const field::FieldDofMap &HydrostaticEquilibriumContext::GetGravityPotentialMap() const noexcept {
return m_gravityPotentialMap;
}
const field::FieldDofMap &HydrostaticEquilibriumContext::GetDisplacementMap() const noexcept {
return m_displacementMap;
}
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 +289,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

View File

@@ -0,0 +1,219 @@
module;
#include <cmath>
#include <mfem.hpp>
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 <typename Dependency>
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::DomainMapper &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

View File

@@ -0,0 +1,225 @@
module;
#include <cmath>
#include <mfem.hpp>
module mean_field;
import :operators.context.rotational_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
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 <typename Dependency>
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::DomainMapper &domainMapper
)
: m_f(f),
m_densityMap(
field::make_field_dof_map<
field::Density,
DomainSchema>(*f.densityFes)
),
m_displacementMap(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
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_densityMap.reduced_size(),
"RotationalDisplacementForceLinearizationContext received a "
"density vector with the wrong size."
);
MFEM_VERIFY(
state.displacement.Size() == m_displacementMap.reduced_size(),
"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.SetSize(m_densityMap.full_size());
m_densityMap.scatter(state.density, m_baseDensityTrue);
report.updatedDensity = true;
}
if (geometryPreparationRequired) {
m_displacementTrue.SetSize(m_displacementMap.full_size());
m_displacementMap.scatter(state.displacement, m_displacementTrue);
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;
}
const field::FieldDofMap &RotationalDisplacementForceLinearizationContext::GetDensityMap() const noexcept {
return m_densityMap;
}
const field::FieldDofMap &RotationalDisplacementForceLinearizationContext::GetDisplacementMap() const noexcept {
return m_displacementMap;
}
void RotationalDisplacementForceLinearizationContext::VerifyPrepared() const {
MFEM_VERIFY(
m_isPrepared, "RotationalDisplacementForceLinearizationContext has not been "
"prepared."
);
}
} // namespace mean_field::operators::context::rotational_displacement_force

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ module;
module mean_field;
import :operators.gravity_field_jacobian;
import :operators.kernels.gravity_field;
import :utils.blocks;
namespace {
@@ -15,9 +14,7 @@ namespace {
) {
const int offset = offsets[index];
const int size = offsets[index + 1] - offset;
return mfem::Vector(
const_cast<mfem::real_t *>(vector.GetData()) + offset, size
);
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + offset, size);
}
template <int index>
@@ -56,10 +53,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 +64,18 @@ namespace {
using form = mean_field::utils::blocks::gravity_field_form;
constexpr auto density_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::density_field.mass_term
);
constexpr auto displacement_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::get_value_block<form>(mean_field::utils::blocks::density_field.mass_term);
constexpr auto displacement_block = mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::displacement_field.geometry_term
);
constexpr auto gravity_gradient_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::gravity_field.gradient_term
);
mean_field::utils::blocks::get_value_block<form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_block =
mean_field::utils::blocks::get_value_block<form>(
mean_field::utils::blocks::gravity_field.poisson_term
);
mean_field::utils::blocks::get_value_block<form>(mean_field::utils::blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual_block =
mean_field::utils::blocks::get_residual_block<form>(
mean_field::utils::blocks::gravity_field.gradient_term
);
mean_field::utils::blocks::get_residual_block<form>(mean_field::utils::blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual_block =
mean_field::utils::blocks::get_residual_block<form>(
mean_field::utils::blocks::gravity_field.poisson_term
);
mean_field::utils::blocks::get_residual_block<form>(mean_field::utils::blocks::gravity_field.poisson_term);
validate_offsets(
state_offsets, form::value_block_count,
@@ -105,34 +88,38 @@ namespace {
"form."
);
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
const auto density_map =
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes);
const auto displacement_map =
mean_field::field::make_field_dof_map<mean_field::field::Displacement, DomainSchema>(*f.displacementFes);
const auto flux_map =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes);
const auto potential_map =
mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes);
MFEM_VERIFY(
get_block_size(state_offsets, density_block) ==
f.densityFes->GetTrueVSize(),
get_block_size(state_offsets, density_block) == density_map.reduced_size(),
"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) == displacement_map.reduced_size(),
"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) == flux_map.reduced_size(),
"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) == potential_map.reduced_size(),
"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) == flux_map.reduced_size(),
"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) == potential_map.reduced_size(),
"The Jacobian Poisson-residual block has the wrong size."
);
}
@@ -141,53 +128,38 @@ namespace {
namespace mean_field::operators {
GravityFieldJacobianOperator::GravityFieldJacobianOperator(
fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext
&linearization_context,
const mfem::Array<int> &state_true_offsets,
const mfem::Array<int> &residual_true_offsets
const mapping::DomainMapper &domain_mapper,
const context::gravity_field::GravityFieldLinearizationContext &linearization_context,
const mfem::Array<int> &state_offsets,
const mfem::Array<int> &residual_offsets
)
: Operator(
residual_true_offsets.Last(),
state_true_offsets.Last()
residual_offsets.Last(),
state_offsets.Last()
),
m_fem(f),
m_domain_mapper(domain_mapper),
m_linearization_context(linearization_context),
m_state_true_offsets(state_true_offsets),
m_residual_true_offsets(residual_true_offsets) {
m_state_offsets(state_offsets),
m_residual_offsets(residual_offsets) {
MFEM_VERIFY(
f.densityFes != nullptr,
"GravityFieldJacobianOperator requires the density finite-element "
f.densityFes != nullptr, "GravityFieldJacobianOperator requires the density finite-element "
"space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"GravityFieldJacobianOperator requires the gravity-potential "
f.gravityPotentialFes != nullptr, "GravityFieldJacobianOperator requires the gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"GravityFieldJacobianOperator requires the "
f.gravityFluxFes != nullptr, "GravityFieldJacobianOperator requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"GravityFieldJacobianOperator requires the "
f.displacementFes != nullptr, "GravityFieldJacobianOperator requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.gravityContext.b_form != nullptr,
"GravityFieldJacobianOperator requires the divergence operator."
);
MFEM_VERIFY(
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(),
@@ -196,7 +168,7 @@ namespace mean_field::operators {
"dimension."
);
validate_layout(f, m_state_true_offsets, m_residual_true_offsets);
validate_layout(f, m_state_offsets, m_residual_offsets);
}
void GravityFieldJacobianOperator::Mult(
@@ -204,110 +176,100 @@ namespace mean_field::operators {
mfem::Vector &action
) const {
MFEM_VERIFY(
m_linearization_context.IsPrepared(),
"GravityFieldJacobianOperator requires a prepared linearization "
m_linearization_context.IsPrepared(), "GravityFieldJacobianOperator requires a prepared linearization "
"context."
);
MFEM_VERIFY(
direction.Size() == Width(),
"GravityFieldJacobianOperator received a direction with the wrong "
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<form>(
utils::blocks::density_field.mass_term
);
constexpr auto density_block = utils::blocks::get_value_block<form>(utils::blocks::density_field.mass_term);
constexpr auto displacement_block =
utils::blocks::get_value_block<form>(
utils::blocks::displacement_field.geometry_term
);
utils::blocks::get_value_block<form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.gradient_term
);
utils::blocks::get_value_block<form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_block =
utils::blocks::get_value_block<form>(
utils::blocks::gravity_field.poisson_term
);
utils::blocks::get_value_block<form>(utils::blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
);
utils::blocks::get_residual_block<form>(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.GetDensityTrue();
const mfem::Vector &gravity_gradient = m_linearization_context.GetGravityGradientTrue();
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_offsets, density_block);
const mfem::Vector displacement_direction =
make_read_only_value_view(direction, m_state_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_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_offsets, gravity_potential_block);
const field::FieldDofMap &displacement_map = m_linearization_context.GetDisplacementMap();
const field::FieldDofMap &flux_map = m_linearization_context.GetGravityGradientMap();
const field::FieldDofMap &potential_map = m_linearization_context.GetGravityPotentialMap();
mfem::Vector displacement_direction_true(displacement_map.full_size());
mfem::Vector gravity_gradient_direction_true(flux_map.full_size());
mfem::Vector gravity_potential_direction_true(potential_map.full_size());
displacement_map.scatter(displacement_direction, displacement_direction_true);
flux_map.scatter(gravity_gradient_direction, gravity_gradient_direction_true);
potential_map.scatter(gravity_potential_direction, gravity_potential_direction_true);
action.SetSize(Height());
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_offsets, gravity_gradient_residual_block);
mfem::Vector gravity_poisson_action =
make_residual_view(action, m_residual_offsets, gravity_poisson_residual_block);
mfem::Vector transpose_divergence_action;
mfem::Vector transpose_divergence_action_true;
mfem::Vector transpose_divergence_action(flux_map.reduced_size());
mfem::Vector divergence_action_true;
mfem::Vector source_action;
mfem::Vector mass_variation_action;
mfem::Vector source_variation_action;
mfem::Vector mass_variation_action_true;
mfem::Vector mass_variation_action(flux_map.reduced_size());
mfem::Vector source_variation_action_true;
mfem::Vector source_variation_action(potential_map.reduced_size());
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
geometry_context.GetMassOperator().MultDisplacementVariationTrue(
gravity_gradient, displacement_direction_true, mass_variation_action_true
);
flux_map.gather(mass_variation_action_true, mass_variation_action);
kernels::apply_mapped_source_variation(
m_fem, m_domain_mapper, density, displacement,
displacement_direction, source_variation_action
geometry_context.GetSourceOperator().MultDisplacementVariationTrue(
density, displacement_direction_true, source_variation_action_true
);
potential_map.gather(source_variation_action_true, source_variation_action);
transpose_divergence_action.SetSize(gravity_gradient_action.Size());
m_fem.gravityContext.BT->Mult(
gravity_potential_direction, transpose_divergence_action
transpose_divergence_action_true.SetSize(flux_map.full_size());
geometry_context.GetTransposeDivergenceOperator().Mult(
gravity_potential_direction_true, transpose_divergence_action_true
);
flux_map.gather(transpose_divergence_action_true, 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
);
divergence_action_true.SetSize(potential_map.full_size());
geometry_context.GetDivergenceOperator().Mult(gravity_gradient_direction_true, divergence_action_true);
potential_map.gather(divergence_action_true, gravity_poisson_action);
gravity_poisson_action -= source_action;
gravity_poisson_action -= source_variation_action;
gravity_gradient_action.SyncAliasMemory(action);
gravity_poisson_action.SyncAliasMemory(action);
}
const context::gravity_field::GravityFieldLinearizationContext &

View File

@@ -8,24 +8,32 @@ module;
module mean_field;
import :operators.kernels.barotropic_closure;
import :field.registry;
import :utils.domain;
namespace {
namespace dimensions = mean_field::dimensions;
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using ClosureDomain = mean_field::field::FieldDomainT<mean_field::field::Density>;
enum class ClosureAction { residual, density, enthalpy };
[[nodiscard]] bool element_is_in_closure_support(const int attribute) {
return DomainSchema::template attribute_belongs_to<ClosureDomain>(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 +47,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;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -57,19 +61,13 @@ namespace {
}
}
int get_eos_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
const double extraOrder =
(barotrope.polytropic_index() - 1.0) *
static_cast<double>(
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<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
extraOrder <= static_cast<double>(std::numeric_limits<int>::max()),
"The EOS effective polynomial order is invalid."
);
@@ -78,43 +76,36 @@ 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<mean_field::field::Enthalpy>;
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
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<int, 1>{get_eos_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::EosClosureSource>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(),
std::array<int, 1>{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 "
resolution.integration_rule != nullptr, "The quadrature policy did not return an "
"EOS-closure integration rule."
);
@@ -123,57 +114,47 @@ namespace {
void validate_common_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::mapping::DomainMapper &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."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The EOS closure kernel requires the density "
f.densityFes != nullptr, "The EOS closure kernel requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The EOS closure kernel requires the enthalpy "
f.enthalpyFes != nullptr, "The EOS closure kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The EOS closure kernel requires the displacement "
f.displacementFes != nullptr, "The EOS closure kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The EOS closure kernel requires the "
f.compactificationFes != nullptr, "The EOS closure kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The EOS closure kernel requires the "
f.compactificationCoordinate != nullptr, "The EOS closure kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The EOS closure kernel requires the quadrature "
f.quadratureFactory != nullptr, "The EOS closure kernel requires the quadrature "
"rule factory."
);
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(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
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::mapping::DomainMapper &domainMapper,
const mean_field::eos::Polytrope &barotrope,
const ClosureAction closureAction,
const mfem::Vector *densityInputTrue,
const mfem::Vector *baseEnthalpyTrue,
@@ -183,29 +164,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 +199,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 +207,7 @@ namespace {
mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
@@ -253,115 +224,78 @@ 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 "
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(
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());
@@ -369,34 +303,26 @@ namespace {
elementAction.SetSize(densityElement.GetDof());
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<int>(mappingStatus)
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(mappingStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
@@ -408,34 +334,35 @@ 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);
const double equationOfStateDensity =
eos::evaluate<dimensions::quantity::Density>(
barotrope, dimensions::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
integrand = density - equationOfStateDensity;
} else {
const double enthalpyVariation =
elementEnthalpyVariation * enthalpyShape;
const double enthalpyVariation = elementEnthalpyVariation * enthalpyShape;
integrand = -barotrope.density_derivative_from_enthalpy(
baseEnthalpy
) *
enthalpyVariation;
const double densityDerivative =
eos::partialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>(
barotrope, dimensions::SpecificEnthalpyValue{baseEnthalpy}
)
.value();
integrand = -densityDerivative * 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);
}
}
@@ -453,52 +380,52 @@ namespace {
namespace mean_field::operators::kernels {
void apply_barotropic_closure(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mapping::DomainMapper &domainMapper,
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 mapping::DomainMapper &domainMapper,
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 mapping::DomainMapper &domainMapper,
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 mapping::DomainMapper &domainMapper,
const eos::Polytrope &barotrope,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
@@ -511,65 +438,54 @@ namespace mean_field::operators::kernels {
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The barotropic-closure displacement action "
f.densityFes != nullptr, "The barotropic-closure displacement action "
"requires the density finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The barotropic-closure displacement action "
f.enthalpyFes != nullptr, "The barotropic-closure displacement action "
"requires the enthalpy finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The barotropic-closure displacement action "
f.displacementFes != nullptr, "The barotropic-closure displacement action "
"requires the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The barotropic-closure displacement action "
f.compactificationFes != nullptr, "The barotropic-closure displacement action "
"requires the compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The barotropic-closure displacement action "
f.compactificationCoordinate != nullptr, "The barotropic-closure displacement action "
"requires the compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The barotropic-closure displacement action "
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 "
domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match the "
"mesh dimension."
);
@@ -584,17 +500,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::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
@@ -614,109 +525,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 "
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());
@@ -726,22 +604,16 @@ namespace mean_field::operators::kernels {
elementAction.SetSize(densityElement.GetDof());
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(
@@ -749,17 +621,13 @@ namespace mean_field::operators::kernels {
"The base mapping is invalid while applying "
"the barotropic-closure displacement action. "
"Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(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(
@@ -767,10 +635,8 @@ namespace mean_field::operators::kernels {
"The mapping variation is invalid while "
"applying the barotropic-closure "
"displacement action. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(variationStatus)
<< elementId << ", attribute: " << transformation->Attribute << ", quadrature point: "
<< quadraturePoint << ", status: " << static_cast<int>(variationStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
@@ -779,19 +645,19 @@ namespace mean_field::operators::kernels {
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 equationOfStateDensity = eos::evaluate<dimensions::quantity::Density>(
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value();
const double geometryActionValue =
closureValue * mappingVariation.weight_variation;
const double closureValue = densityValue - equationOfStateDensity;
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."
);

View File

@@ -0,0 +1,723 @@
module;
#include <array>
#include <cmath>
#include <optional>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.gravity_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
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<mean_field::field::Displacement>;
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::field::Displacement::Form::GravityForce>(
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::DomainMapper &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::DomainMapper &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::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> 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 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 (is_vacuum_attribute(transformation->Attribute)) {
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<mean_field::mapping::ElementDisplacementData> 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<int>(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<int>(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::DomainMapper &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::DomainMapper &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::DomainMapper &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::DomainMapper &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::DomainMapper &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

File diff suppressed because it is too large Load Diff

View File

@@ -11,20 +11,22 @@ module mean_field;
import :operators.kernels.hydrostatic_equilibrium;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(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);
@@ -38,17 +40,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;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -59,11 +57,9 @@ namespace {
void validate_fem(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper
const mean_field::mapping::DomainMapper &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,8 +67,7 @@ namespace {
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"The hydrostatic kernel requires the "
f.gravityPotentialFes != nullptr, "The hydrostatic kernel requires the "
"gravity-potential finite-element space."
);
@@ -82,32 +77,27 @@ namespace {
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The hydrostatic kernel requires the "
f.compactificationFes != nullptr, "The hydrostatic kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The hydrostatic kernel requires the "
f.compactificationCoordinate != nullptr, "The hydrostatic kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The hydrostatic kernel requires the "
f.quadratureFactory != nullptr, "The hydrostatic kernel requires the "
"quadrature-rule factory."
);
MFEM_VERIFY(
f.mesh->Dimension() == 3,
"The rigid-rotation hydrostatic kernel "
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 "
domainMapper.GetDimension() == f.mesh->Dimension(), "The domain-mapper dimension does not match "
"the mesh dimension."
);
}
@@ -118,69 +108,51 @@ namespace {
const mfem::FiniteElement &potentialElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
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::field::Enthalpy::Form::EquilibriumEnthalpy>(
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::field::Enthalpy::Form::EquilibriumGravity>(
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<int, 1>{2},
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<int, 1>{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::field::Enthalpy::Form::EquilibriumConstant>(
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 "
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 +160,7 @@ namespace {
update_order(rotationQuery);
update_order(constantQuery);
return mfem::IntRules.Get(
transformation.GetGeometryType(), integrationOrder
);
return mfem::IntRules.Get(transformation.GetGeometryType(), integrationOrder);
}
struct HydrostaticAssemblyRequest {
@@ -211,7 +181,7 @@ namespace {
void assemble_hydrostatic_form(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::mapping::DomainMapper &domainMapper,
const mfem::Vector &displacementTrue,
const HydrostaticAssemblyRequest &request,
mfem::Vector &result
@@ -219,81 +189,64 @@ namespace {
validate_fem(f, domainMapper);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The hydrostatic displacement vector has "
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 "
request.rotation != nullptr, "The hydrostatic residual or geometry "
"action requires the rotation model."
);
MFEM_VERIFY(
request.baseEnthalpyTrue != nullptr,
"The hydrostatic residual or geometry "
request.baseEnthalpyTrue != nullptr, "The hydrostatic residual or geometry "
"action requires the base enthalpy."
);
MFEM_VERIFY(
request.basePotentialTrue != nullptr,
"The hydrostatic residual or geometry "
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 +261,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::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> enthalpyDofs;
mfem::Array<int> potentialDofs;
@@ -366,36 +303,27 @@ namespace {
mfem::Vector enthalpyShape;
mfem::Vector potentialShape;
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 "
transformation != nullptr, "The hydrostatic kernel received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
if (is_vacuum_attribute(transformation->Attribute)) {
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,117 +332,80 @@ 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(
const mean_field::mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
.displacement = displacementData, .compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
std::optional<mean_field::mapping::ElementDisplacementData> displacementVariationData;
if (request.displacementVariationTrue != nullptr) {
displacementVariationData.emplace(
mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
mean_field::mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation
)
);
@@ -528,32 +419,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<int>(mappingStatus)
);
@@ -564,27 +448,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,44 +467,34 @@ 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,
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 -
weightedVariation += baseIntegrand * mappingVariation.weight_variation -
rotationVariation * mappingContext.quadrature.weight;
}
@@ -650,7 +515,7 @@ namespace {
namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue,
@@ -666,14 +531,12 @@ 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(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
@@ -682,14 +545,12 @@ 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(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
@@ -698,14 +559,12 @@ 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(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const double constantVariation,
const mfem::Vector &displacementTrue,
mfem::Vector &action
@@ -714,14 +573,12 @@ 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(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
@@ -738,14 +595,12 @@ 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(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mapping::DomainMapper &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
@@ -768,8 +623,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

View File

@@ -3,6 +3,7 @@ module;
#include <array>
#include <cmath>
#include <limits>
#include <optional>
#include <mfem.hpp>
@@ -11,20 +12,29 @@ module mean_field;
import :operators.kernels.pressure_force;
namespace {
namespace dimensions = mean_field::dimensions;
namespace eos = mean_field::eos;
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
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 +49,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;
const mfem::Operator *prolongation =
finiteElementSpace.GetProlongationMatrix();
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
@@ -68,15 +76,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 +94,11 @@ namespace {
* contribution is therefore n times that order.
*/
const double extraOrder =
barotrope.polytropic_index() *
static_cast<double>(
mean_field::field::Enthalpy::Scalar::familyOrder
);
barotrope.polytropic_index() * static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <=
static_cast<double>(std::numeric_limits<int>::max()),
extraOrder <= static_cast<double>(std::numeric_limits<int>::max()),
"The pressure EOS effective polynomial order is invalid."
);
@@ -104,43 +107,36 @@ 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<mean_field::field::Enthalpy>;
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
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<int, 1>{get_pressure_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(),
std::array<int, 1>{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 "
rule.integration_rule != nullptr, "The quadrature policy did not return a pressure-force "
"integration rule."
);
@@ -149,41 +145,34 @@ namespace {
void validate_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::mapping::DomainMapper &domainMapper,
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.");
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The pressure-force kernel requires the enthalpy "
f.enthalpyFes != nullptr, "The pressure-force kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The pressure-force kernel requires the displacement "
f.displacementFes != nullptr, "The pressure-force kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The pressure-force kernel requires the compactification "
f.compactificationFes != nullptr, "The pressure-force kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The pressure-force kernel requires the compactification "
f.compactificationCoordinate != nullptr, "The pressure-force kernel requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The pressure-force kernel requires the quadrature "
f.quadratureFactory != nullptr, "The pressure-force kernel requires the quadrature "
"rule factory."
);
@@ -204,72 +193,103 @@ namespace {
);
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The displacement vector dimension does not match the "
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::DomainMapper &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<int> enthalpyDofs;
mean_field::mapping::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> enthalpyDofsofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> 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<int> 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 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 != nullptr, "The pressure-force kernel received a null element "
"transformation."
);
@@ -277,132 +297,141 @@ namespace mean_field::operators::kernels {
* Skip vacuum before constructing or evaluating any mapping
* data for the element.
*/
if (transformation->Attribute == vacuumAttribute) {
if (is_vacuum_attribute(transformation->Attribute)) {
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<mean_field::mapping::ElementDisplacementData> 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<int>(mappingStatus)
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex << ", status: " << static_cast<int>(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 = eos::evaluate<dimensions::quantity::Pressure>(
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value();
} else {
const double enthalpyVariationValue = elementEnthalpyVariation * enthalpyShape;
pressureFactor = eos::partialDerivative<eos::quantity::Pressure, eos::quantity::SpecificEnthalpy>(
barotrope, dimensions::SpecificEnthalpyValue{enthalpyValue}
)
.value() *
enthalpyVariationValue;
}
displacementElement.CalcDShape(integrationPoint, displacementDShapeReference);
/*
* Row i of DShape is grad_reference(N_i). Multiplication
@@ -411,17 +440,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);
std::optional<mean_field::mapping::VolumeMappingVariation> mappingVariation;
if (pressureForceAction == PressureForceAction::displacement) {
mappingVariation.emplace();
const mean_field::mapping::MappingStatus variationStatus = domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData, *transformation, integrationPoint, mappingContext,
workspace, *mappingVariation
);
const double weightedPressure =
pressureValue * mappingContext.quadrature.weight;
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<int>(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 +493,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
namespace mean_field::operators::kernels {
void apply_pressure_force_residual(
const fem::FEM &f,
const mapping::DomainMapper &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::DomainMapper &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::DomainMapper &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

View File

@@ -0,0 +1,595 @@
module;
#include <array>
#include <cmath>
#include <optional>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.rotational_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
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<mean_field::field::Displacement>;
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::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{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::DomainMapper &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::DomainMapper &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::DomainMapper::Workspace workspace(f.mesh->Dimension());
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> 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 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 (is_vacuum_attribute(transformation->Attribute)) {
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<mean_field::mapping::ElementDisplacementData> 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<int>(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<int>(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::DomainMapper &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::DomainMapper &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::DomainMapper &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::DomainMapper &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

View File

@@ -0,0 +1,590 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_angular_momentum;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
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;
}
}
[[nodiscard]] const mfem::IntegrationRule &get_moment_of_inertia_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
const mfem::ElementTransformation &transformation
) {
using DensityField = mean_field::field::Field<mean_field::field::Density>;
MFEM_VERIFY(
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The angular-momentum element does not match the registered density field."
);
const mean_field::quadrature::Query query =
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{2},
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 an angular-momentum integration rule."
);
return *resolution.integration_rule;
}
void validate_shared_gravity_revisions(
const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext,
const mean_field::operators::AngularMomentumDependencies &dependencies
) {
MFEM_VERIFY(
gravityContext.IsPrepared(),
"PreparedAngularMomentumOperator requires the shared gravity 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,
"PreparedAngularMomentumOperator received revisions that do not match the shared gravity context."
);
}
void validate_identity_transition(
const mean_field::operators::AngularMomentumDependencyStamp &prepared,
const mean_field::operators::AngularMomentumDependencyStamp &requested,
const char *message
) {
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
}
} // namespace
namespace mean_field::operators {
PreparedAngularMomentumOperator::PreparedAngularMomentumOperator(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext,
models::CompiledFixedAngularMomentum constraint
)
: m_fem(f),
m_domainMapper(domainMapper),
m_gravityContext(gravityContext),
m_constraint(std::move(constraint)) {
MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedAngularMomentumOperator requires a mesh.");
MFEM_VERIFY(
m_fem.mesh->Dimension() == 3 && m_domainMapper.GetDimension() == 3,
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
);
MFEM_VERIFY(
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr &&
m_fem.quadratureFactory != nullptr,
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
);
MFEM_VERIFY(
m_gravityContext.GetDensityMap().full_size() == m_fem.densityFes->GetTrueVSize() &&
m_gravityContext.GetDisplacementMap().full_size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedAngularMomentumOperator received incompatible shared FieldDof maps."
);
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
}
PreparedAngularMomentumReport PreparedAngularMomentumOperator::Prepare(
const double angularVelocity,
const AngularMomentumDependencies &dependencies
) {
MFEM_VERIFY(
std::isfinite(angularVelocity),
"PreparedAngularMomentumOperator requires a finite angular-velocity coordinate."
);
validate_shared_gravity_revisions(m_gravityContext, dependencies);
if (m_isPrepared) {
validate_identity_transition(
m_preparedDependencies.discretization,
dependencies.discretization,
"A new angular-momentum discretization identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.density,
dependencies.density,
"A new angular-momentum density identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.displacement,
dependencies.displacement,
"A new angular-momentum displacement identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.rotation,
dependencies.rotation,
"A new angular-momentum rotation identity must change its 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 updateAngularVelocity =
!m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
angularVelocity != m_angularVelocity;
m_isPrepared = false;
PreparedAngularMomentumReport report;
if (rebuildStaticPlan) {
BuildStaticPlan();
report.rebuiltStaticPlan = true;
}
if (refreshGeometry) {
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
report.refreshedGeometry = true;
}
if (refreshDensity) {
RefreshDensity(m_gravityContext.GetDensityTrue());
report.refreshedDensity = true;
}
if (updateAngularVelocity) {
m_angularVelocity = angularVelocity;
report.updatedAngularVelocity = true;
}
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
AssembleResidual();
report.assembledResidual = true;
}
m_preparedDependencies = dependencies;
m_isPrepared = true;
return report;
}
void PreparedAngularMomentumOperator::BuildStaticPlan() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
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, "Angular-momentum preparation received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
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_moment_of_inertia_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, "PreparedAngularMomentumOperator found no stellar elements.");
}
void PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
MFEM_VERIFY(
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
"Angular-momentum geometry has the wrong displacement size."
);
validate_finite_vector(displacement, "Angular-momentum geometry contains a non-finite displacement.");
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
mapping::DomainMapper::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 && !point.mappingContext.mapping.compactified,
"Mapped angular-momentum geometry is invalid. Element: " << data.elementId
);
point.cylindricalRadiusSquared =
CylindricalRadiusSquared(point.mappingContext.mapping.physical_position);
}
}
}
void PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
MFEM_VERIFY(
density.Size() == m_fem.densityFes->GetTrueVSize(),
"Angular-momentum density has the wrong size."
);
validate_finite_vector(density, "Angular-momentum density contains a non-finite 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), "Angular-momentum quadrature density is non-finite.");
}
}
}
void PreparedAngularMomentumOperator::AssembleResidual() {
double localMomentOfInertia = 0.0;
for (const ElementPAData &data : m_elements) {
for (const QuadraturePointData &point : data.quadraturePoints) {
localMomentOfInertia += point.density * point.cylindricalRadiusSquared *
point.mappingContext.quadrature.weight;
}
}
m_momentOfInertia = GlobalSum(localMomentOfInertia);
MFEM_VERIFY(
std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0,
"PreparedAngularMomentumOperator assembled an invalid moment of inertia."
);
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
m_cachedResidual.SetSize(1);
m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value();
++m_preparationCount;
}
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
++m_residualApplicationCount;
}
double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(
const mfem::Vector &densityVariation
) const {
MFEM_VERIFY(
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
"Angular-momentum density action has the wrong true-vector size."
);
true_to_local(*m_fem.densityFes, densityVariation, m_densityVariationLocal);
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
for (const QuadraturePointData &point : data.quadraturePoints) {
localAction += (m_elementDensityVariation * point.densityShape) *
point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
}
}
return localAction;
}
double PreparedAngularMomentumOperator::EvaluateDisplacementMomentActionLocal(
const mfem::Vector &displacementVariation
) const {
MFEM_VERIFY(
displacementVariation.Size() == m_fem.displacementFes->GetTrueVSize(),
"Angular-momentum displacement action has the wrong true-vector size."
);
true_to_local(*m_fem.displacementFes, displacementVariation, m_displacementVariationLocal);
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_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, m_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) {
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
mappingData,
directionData,
*transformation,
point.integrationPoint,
point.mappingContext,
workspace,
variation
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
);
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
point.mappingContext.mapping.physical_position,
variation.mapping.physical_position_variation
);
localAction += point.density *
(radiusSquaredVariation * point.mappingContext.quadrature.weight +
point.cylindricalRadiusSquared * variation.weight_variation);
}
}
return localAction;
}
void PreparedAngularMomentumOperator::ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Angular-momentum density action has the wrong reduced size."
);
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
++m_actionStatistics.densityApplications;
}
void PreparedAngularMomentumOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Angular-momentum displacement action has the wrong reduced size."
);
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity *
GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
++m_actionStatistics.displacementApplications;
}
void PreparedAngularMomentumOperator::ApplyAngularVelocityJacobianAction(
const double angularVelocityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
action.SetSize(1);
action(0) = m_momentOfInertia * angularVelocityVariation;
++m_actionStatistics.angularVelocityApplications;
}
void PreparedAngularMomentumOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
const double angularVelocityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size() &&
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Angular-momentum complete action has incompatible reduced coordinates."
);
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity * GlobalSum(localMomentAction) +
m_momentOfInertia * angularVelocityVariation;
++m_actionStatistics.completeApplications;
}
double PreparedAngularMomentumOperator::CylindricalRadiusSquared(
const mfem::Vector &physicalPosition
) const noexcept {
const auto &axis = m_constraint.specification().axis();
const auto &center = m_constraint.specification().center();
double radiusSquared = 0.0;
double axialPosition = 0.0;
for (int component = 0; component < 3; ++component) {
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
radiusSquared += relative * relative;
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
}
return std::max(0.0, radiusSquared - axialPosition * axialPosition);
}
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
const mfem::Vector &physicalPosition,
const mfem::Vector &physicalPositionVariation
) const noexcept {
const auto &axis = m_constraint.specification().axis();
const auto &center = m_constraint.specification().center();
double relativeDotVariation = 0.0;
double axialPosition = 0.0;
double axialVariation = 0.0;
for (int component = 0; component < 3; ++component) {
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
relativeDotVariation += relative * physicalPositionVariation(component);
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
axialVariation += axis[static_cast<std::size_t>(component)] * physicalPositionVariation(component);
}
return 2.0 * (relativeDotVariation - axialPosition * axialVariation);
}
double PreparedAngularMomentumOperator::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 PreparedAngularMomentumOperator::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 PreparedAngularMomentumOperator::GetMomentOfInertia() const {
VerifyPrepared();
return m_momentOfInertia;
}
double PreparedAngularMomentumOperator::GetAngularVelocity() const {
VerifyPrepared();
return m_angularVelocity;
}
double PreparedAngularMomentumOperator::GetCurrentAngularMomentum() const {
VerifyPrepared();
return m_currentAngularMomentum;
}
double PreparedAngularMomentumOperator::GetTargetAngularMomentum() const noexcept {
return m_constraint.targetAngularMomentum().value();
}
physics::RigidRotation PreparedAngularMomentumOperator::GetRotation() const {
VerifyPrepared();
return m_constraint.makeRotation(m_angularVelocity);
}
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
VerifyPrepared();
const double target = GetTargetAngularMomentum();
const double residual = m_currentAngularMomentum - target;
return {
.targetAngularMomentum = target,
.achievedAngularMomentum = m_currentAngularMomentum,
.momentOfInertia = m_momentOfInertia,
.angularVelocity = m_angularVelocity,
.dimensionalResidual = residual,
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
};
}
std::uint64_t PreparedAngularMomentumOperator::GetPreparationCount() const noexcept {
return m_preparationCount;
}
std::uint64_t PreparedAngularMomentumOperator::GetResidualApplicationCount() const noexcept {
return m_residualApplicationCount;
}
const PreparedAngularMomentumActionStatistics &
PreparedAngularMomentumOperator::GetActionStatistics() const noexcept {
return m_actionStatistics;
}
const models::CompiledFixedAngularMomentum &
PreparedAngularMomentumOperator::GetCompiledConstraint() const noexcept {
return m_constraint;
}
void PreparedAngularMomentumOperator::VerifyPrepared() const {
MFEM_VERIFY(IsPrepared(), "The angular-momentum invariant must be prepared before application.");
}
} // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
module;
#include <mfem.hpp>
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::DomainMapper &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.GetDensityMap().gather(m_gravityContext.GetDensityTrue());
const mfem::Vector displacement =
m_gravityContext.GetDisplacementMap().gather(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
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_gravityContext.GetDisplacementMap().reduced_size()) {
AssembleResidual();
report.assembledResidual = true;
}
MFEM_VERIFY(
m_cachedResidual.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto barotropicConstantValue =
utils::blocks::get_value_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
MFEM_VERIFY(
m_layout.size(densityValue) == m_preparedOperator.GetGravityContext().GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) ==
m_preparedOperator.GetGravityContext().GetDisplacementMap().reduced_size() &&
m_layout.size(gravityGradientValue) ==
m_preparedOperator.GetGravityContext().GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialValue) ==
m_preparedOperator.GetGravityContext().GetGravityPotentialMap().reduced_size() &&
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) ==
m_preparedOperator.GetGravityContext().GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialResidual) ==
m_preparedOperator.GetGravityContext().GetGravityPotentialMap().reduced_size() &&
m_layout.size(densityResidual) ==
m_preparedOperator.GetGravityContext().GetDensityMap().reduced_size() &&
m_layout.size(displacementResidual) ==
m_preparedOperator.GetGravityContext().GetDisplacementMap().reduced_size() &&
m_layout.size(enthalpyResidual) == m_preparedOperator.GetPressureOperator().GetEnthalpySize() &&
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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
const mfem::Vector densityVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue)
);
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(displacementValue),
m_layout.size(displacementValue)
);
const mfem::Vector gravityGradientVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(gravityGradientValue),
m_layout.size(gravityGradientValue)
);
const mfem::Vector enthalpyVariation(
const_cast<mfem::real_t *>(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

View File

@@ -0,0 +1,610 @@
module;
#include <mfem.hpp>
module mean_field;
import :operators.kernels.gravity_displacement_force;
import :operators.prepared_gravity_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[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;
}
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
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
) {
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;
}
MFEM_VERIFY(ordering == mfem::Ordering::byVDIM, "Unsupported displacement ordering.");
return scalarDof * dimension + component;
}
[[nodiscard]] const mfem::IntegrationRule &get_gravity_force_rule(
const mean_field::fem::FEM &f,
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::GravityForce>(
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;
}
} // namespace
namespace mean_field::operators {
PreparedGravityDisplacementForceOperator::PreparedGravityDisplacementForceOperator(
const fem::FEM &f,
const mapping::DomainMapper &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."
);
}
void PreparedGravityDisplacementForceOperator::PrepareElementData() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector baseDensityLocal;
mfem::Vector baseGravityGradientLocal;
mfem::Vector baseDisplacementLocal;
true_to_local(*m_fem.densityFes, m_gravityContext.GetDensityTrue(), baseDensityLocal);
true_to_local(*m_fem.gravityFluxFes, m_gravityContext.GetGravityGradientTrue(), baseGravityGradientLocal);
true_to_local(
*m_fem.displacementFes, m_gravityContext.GetGeometryContext().GetDisplacementTrue(), baseDisplacementLocal
);
mapping::DomainMapper::Workspace workspace(m_domainMapper.GetDimension());
mapping::VolumeMappingContext mappingContext;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseGravityGradient;
mfem::Vector elementBaseDisplacement;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector baseGravityReferenceValue;
mfem::DenseMatrix gravityGradientShape;
const int dimension = m_domainMapper.GetDimension();
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared gravity force received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.gravityGradientDofTransformation =
m_fem.gravityFluxFes->GetElementVDofs(elementId, data.gravityGradientDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseGravityGradientLocal.GetSubVector(data.gravityGradientDofs, elementBaseGravityGradient);
baseDisplacementLocal.GetSubVector(data.displacementDofs, elementBaseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(elementBaseDensity);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(elementBaseGravityGradient);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementBaseDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
data.integrationRule = &get_gravity_force_rule(m_fem, *transformation);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementBaseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
data.mappingJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.inverseMeshJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.baseGravityReferenceValues.SetSize(quadraturePointCount, dimension);
data.baseDensityValues.SetSize(quadraturePointCount);
data.referenceWeights.SetSize(quadraturePointCount);
densityShape.SetSize(densityElement.GetDof());
gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
baseGravityReferenceValue.SetSize(dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
"Prepared gravity force encountered an invalid stellar mapping."
);
densityElement.CalcShape(integrationPoint, densityShape);
gravityGradientElement.CalcVShape(*transformation, gravityGradientShape);
gravityGradientShape.MultTranspose(elementBaseGravityGradient, baseGravityReferenceValue);
data.baseDensityValues(quadraturePoint) = elementBaseDensity * densityShape;
data.referenceWeights(quadraturePoint) = integrationPoint.weight * transformation->Weight();
const mfem::DenseMatrix &inverseMeshJacobian = transformation->InverseJacobian();
for (int row = 0; row < dimension; ++row) {
data.baseGravityReferenceValues(quadraturePoint, row) = baseGravityReferenceValue(row);
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
data.mappingJacobians(quadraturePoint, entry) =
mappingContext.mapping.mapping_jacobian(row, column);
data.inverseMeshJacobians(quadraturePoint, entry) = inverseMeshJacobian(row, column);
}
}
}
}
}
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.GetDensityTrue(), m_gravityContext.GetGravityGradientTrue(),
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
);
m_cachedResidual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
PrepareElementData();
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();
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
kernels::apply_gravity_displacement_force_density_action(
m_fem, m_domainMapper, m_densityVariationTrue, m_gravityContext.GetGravityGradientTrue(),
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
);
action.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, action);
++m_densityJacobianStatistics.applications;
}
void PreparedGravityDisplacementForceOperator::ApplyGravityGradientJacobianAction(
const mfem::Vector &gravityGradientVariation,
mfem::Vector &action
) const {
VerifyPrepared();
m_gravityGradientVariationTrue.SetSize(m_gravityContext.GetGravityGradientMap().full_size());
m_gravityContext.GetGravityGradientMap().scatter(gravityGradientVariation, m_gravityGradientVariationTrue);
kernels::apply_gravity_displacement_force_gradient_action(
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_gravityGradientVariationTrue,
m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
);
action.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, action);
++m_gravityGradientJacobianStatistics.applications;
}
void PreparedGravityDisplacementForceOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
kernels::apply_gravity_displacement_force_displacement_action(
m_fem, m_domainMapper, m_gravityContext.GetDensityTrue(), m_gravityContext.GetGravityGradientTrue(),
m_displacementVariationTrue, m_gravityContext.GetGeometryContext().GetDisplacementTrue(), m_actionTrue
);
action.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, action);
++m_displacementJacobianStatistics.applications;
}
void PreparedGravityDisplacementForceOperator::ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &gravityGradientVariationTrue,
mfem::Vector &actionTrue
) const {
true_to_local(*m_fem.densityFes, densityVariationTrue, m_densityVariationLocal);
true_to_local(*m_fem.gravityFluxFes, gravityGradientVariationTrue, m_gravityGradientVariationLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localAction.SetSize(m_fem.displacementFes->GetVSize());
m_localAction = 0.0;
const int dimension = m_domainMapper.GetDimension();
const mfem::Ordering::Type ordering = m_fem.displacementFes->GetOrdering();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared gravity force has no integration rule.");
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
m_gravityGradientVariationLocal.GetSubVector(data.gravityGradientDofs, m_elementGravityGradientVariation);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(m_elementGravityGradientVariation);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(data.elementId);
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared gravity force received a null transformation.");
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
const int scalarDisplacementDofCount = displacementElement.GetDof();
m_densityShape.SetSize(densityElement.GetDof());
m_displacementShape.SetSize(scalarDisplacementDofCount);
m_gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
m_referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
m_referenceDisplacementJacobian.SetSize(dimension, dimension);
m_displacementJacobianVariation.SetSize(dimension, dimension);
m_mappingJacobian.SetSize(dimension, dimension);
m_inverseMeshJacobian.SetSize(dimension, dimension);
m_baseGravityReferenceValue.SetSize(dimension);
m_gravityVariationReferenceValue.SetSize(dimension);
m_mappedBaseGravity.SetSize(dimension);
m_mappedGravityVariation.SetSize(dimension);
m_mappedGeometryVariation.SetSize(dimension);
m_forceValue.SetSize(dimension);
m_elementAction.SetSize(data.displacementDofs.Size());
m_elementAction = 0.0;
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
densityElement.CalcShape(integrationPoint, m_densityShape);
displacementElement.CalcShape(integrationPoint, m_displacementShape);
displacementElement.CalcDShape(integrationPoint, m_referenceDisplacementDShape);
mfem::MultAtB(directionDofs, m_referenceDisplacementDShape, m_referenceDisplacementJacobian);
transformation->SetIntPoint(&integrationPoint);
gravityGradientElement.CalcVShape(*transformation, m_gravityGradientShape);
m_gravityGradientShape.MultTranspose(
m_elementGravityGradientVariation, m_gravityVariationReferenceValue
);
for (int row = 0; row < dimension; ++row) {
m_baseGravityReferenceValue(row) = data.baseGravityReferenceValues(quadraturePoint, row);
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
m_mappingJacobian(row, column) = data.mappingJacobians(quadraturePoint, entry);
m_inverseMeshJacobian(row, column) = data.inverseMeshJacobians(quadraturePoint, entry);
}
}
mfem::Mult(m_referenceDisplacementJacobian, m_inverseMeshJacobian, m_displacementJacobianVariation);
m_mappingJacobian.Mult(m_baseGravityReferenceValue, m_mappedBaseGravity);
m_mappingJacobian.Mult(m_gravityVariationReferenceValue, m_mappedGravityVariation);
m_displacementJacobianVariation.Mult(m_baseGravityReferenceValue, m_mappedGeometryVariation);
const double densityVariationValue = m_elementDensityVariation * m_densityShape;
const double baseDensityValue = data.baseDensityValues(quadraturePoint);
m_forceValue = 0.0;
m_forceValue.Add(densityVariationValue, m_mappedBaseGravity);
m_forceValue.Add(baseDensityValue, m_mappedGravityVariation);
m_forceValue.Add(baseDensityValue, m_mappedGeometryVariation);
m_forceValue *= data.referenceWeights(quadraturePoint);
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(ordering, scalarDof, component, scalarDisplacementDofCount, dimension);
m_elementAction(vectorDof) += m_displacementShape(scalarDof) * m_forceValue(component);
}
}
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(m_elementAction);
}
m_localAction.AddElementVector(data.displacementDofs, m_elementAction);
}
local_to_true(*m_fem.displacementFes, m_localAction, actionTrue);
}
void PreparedGravityDisplacementForceOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
const mfem::Vector &gravityGradientVariation,
mfem::Vector &action
) const {
VerifyPrepared();
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_gravityGradientVariationTrue.SetSize(m_gravityContext.GetGravityGradientMap().full_size());
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_gravityContext.GetGravityGradientMap().scatter(gravityGradientVariation, m_gravityGradientVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
ApplyPreparedCompleteJacobianActionTrue(
m_densityVariationTrue, m_displacementVariationTrue, m_gravityGradientVariationTrue, m_actionTrue
);
action.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(m_actionTrue, 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) {
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
MFEM_VERIFY(
m_layout.size(densityValue) == m_preparedOperator.GetGravityContext().GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) ==
m_preparedOperator.GetGravityContext().GetDisplacementMap().reduced_size() &&
m_layout.size(gravityGradientValue) ==
m_preparedOperator.GetGravityContext().GetGravityGradientMap().reduced_size() &&
m_layout.size(displacementResidual) ==
m_preparedOperator.GetGravityContext().GetDisplacementMap().reduced_size(),
"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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
const mfem::Vector densityVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue)
);
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(displacementValue),
m_layout.size(displacementValue)
);
const mfem::Vector gravityGradientVariation(
const_cast<mfem::real_t *>(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

View File

@@ -1,4 +1,5 @@
module;
#include "profile.h"
#include <cmath>
#include <cstdint>
#include <memory>
@@ -9,23 +10,25 @@ module mean_field;
import :operators.prepared_gravity_source;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_height(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
return f.gravityPotentialFes->GetTrueVSize();
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityPotentialFes)
.reduced_size();
}
int get_operator_width(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
return f.densityFes->GetTrueVSize();
return mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*f.densityFes)
.reduced_size();
}
void true_to_local(
@@ -35,8 +38,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 +52,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;
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,46 +72,37 @@ namespace {
const mfem::FiniteElement &potential_element,
const mfem::ElementTransformation &transformation
) {
using GravityField =
mean_field::field::Field<mean_field::field::Gravity>;
using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
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::field::Gravity::Form::SourceProjection>(
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,
const mean_field::mapping::DomainMapperStateless &domain_mapper,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true
)
: 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 +117,57 @@ namespace {
"Mapped gravity source coefficient received an invalid element "
"ID."
);
if (transformation.Attribute ==
m_domain_mapper.GetVacuumElementAttribute()) {
if (DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
transformation.Attribute
)) {
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<int>(status)
<< "\nElement ID = " << element_id
<< "\nMapping status = " << static_cast<int>(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<int>(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<int>(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 +175,13 @@ namespace {
"non-finite mapping determinant."
);
return 4.0 * std::numbers::pi * mean_field::utils::G *
mapping_determinant;
m_inverse_element_jacobian = mapping_context.quadrature.J_inv;
return 4.0 * std::numbers::pi * mean_field::utils::G * mapping_determinant;
}
[[nodiscard]] const mfem::DenseMatrix &GetInverseElementJacobian() const noexcept {
return m_inverse_element_jacobian;
}
private:
@@ -218,48 +190,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::ElementDisplacementData>(
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<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
@@ -267,7 +223,7 @@ namespace {
}
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local;
@@ -277,12 +233,11 @@ namespace {
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
mean_field::mapping::DomainMapper::Workspace m_workspace;
mfem::DenseMatrix m_inverse_element_jacobian;
int m_cached_element_id{-1};
};
} // namespace
@@ -290,37 +245,45 @@ namespace {
namespace mean_field::operators {
PreparedMappedGravitySourceOperator::PreparedMappedGravitySourceOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
const mapping::DomainMapper &domain_mapper
)
: Operator(
get_operator_height(f),
get_operator_width(f)
),
m_fem(f),
m_domain_mapper(domain_mapper) {
m_domain_mapper(domain_mapper),
m_density_map(
field::make_field_dof_map<
field::Density,
DomainSchema>(*f.densityFes)
),
m_potential_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityPotentialFes)
),
m_displacement_map(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedGravitySourceOperator requires a mesh.");
MFEM_VERIFY(
f.mesh != nullptr,
"PreparedMappedGravitySourceOperator requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"PreparedMappedGravitySourceOperator requires the density "
f.densityFes != nullptr, "PreparedMappedGravitySourceOperator requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"PreparedMappedGravitySourceOperator requires the "
f.gravityPotentialFes != nullptr, "PreparedMappedGravitySourceOperator requires the "
"gravity-potential "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"PreparedMappedGravitySourceOperator requires "
f.displacementFes != nullptr, "PreparedMappedGravitySourceOperator requires "
"the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"PreparedMappedGravitySourceOperator requires the compactification "
f.compactificationFes != nullptr, "PreparedMappedGravitySourceOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
@@ -329,8 +292,7 @@ namespace mean_field::operators {
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"PreparedMappedGravitySourceOperator "
f.quadratureFactory != nullptr, "PreparedMappedGravitySourceOperator "
"requires the quadrature-rule factory."
);
MFEM_VERIFY(
@@ -339,43 +301,50 @@ namespace mean_field::operators {
"dimension."
);
utils::populate_element_mask(
f.mesh.get(), utils::DOMAINS::STELLAR, m_stellar_marker
);
m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
}
void PreparedMappedGravitySourceOperator::Prepare(
const mfem::Vector &displacement_true
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare linearization", 0);
PrepareImpl(displacement, PreparationMode::linearization);
}
void PreparedMappedGravitySourceOperator::PreparePrimal(const mfem::Vector &displacement) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare primal", 0);
PrepareImpl(displacement, PreparationMode::primal);
}
void PreparedMappedGravitySourceOperator::PrepareImpl(
const mfem::Vector &displacement,
const PreparationMode mode
) {
MFEM_VERIFY(
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedGravitySourceOperator received a displacement "
"vector "
"with the wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"PreparedMappedGravitySourceOperator received a non-finite "
std::isfinite(displacement(i)), "PreparedMappedGravitySourceOperator received a non-finite "
"displacement value."
);
}
m_is_prepared = false;
m_has_variation_data = false;
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
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, m_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;
}
@@ -384,26 +353,25 @@ namespace mean_field::operators {
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);
if (mode == PreparationMode::linearization) {
data.displacement_dof_transformation =
m_fem.displacementFes->GetElementVDofs(element_id, data.displacement_dofs);
}
const mfem::FiniteElement &potential_element =
*m_fem.gravityPotentialFes->GetFE(element_id);
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
mfem::ElementTransformation &transformation =
*m_fem.mesh->GetElementTransformation(element_id);
const mfem::FiniteElement &potential_element = *m_fem.gravityPotentialFes->GetFE(element_id);
const mfem::IntegrationRule &integration_rule = get_source_rule(
m_fem, density_element, potential_element, transformation
);
mfem::ElementTransformation &transformation = *m_fem.mesh->GetElementTransformation(element_id);
const mfem::IntegrationRule &integration_rule =
get_source_rule(m_fem, density_element, potential_element, transformation);
data.integration_rule = &integration_rule;
const int quadrature_point_count = integration_rule.GetNPoints();
@@ -411,24 +379,22 @@ 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);
const int dimension = m_fem.mesh->Dimension();
if (mode == PreparationMode::linearization) {
data.inverse_element_jacobians.SetSize(quadrature_point_count, dimension * dimension);
}
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,182 +402,284 @@ 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);
if (mode == PreparationMode::linearization) {
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
data.inverse_element_jacobians(quadrature_point, row * dimension + column) =
inverse_element_jacobian(row, column);
}
}
}
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_has_variation_data = mode == PreparationMode::linearization;
++m_preparation_count;
}
void PreparedMappedGravitySourceOperator::Mult(
const mfem::Vector &density_true,
const mfem::Vector &density,
mfem::Vector &action
) const {
MEAN_FIELD_PROFILE_SCOPE("PreparedMappedGravitySourceOperator::Mult");
MFEM_VERIFY(
m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"Mult is called."
);
MFEM_VERIFY(
density_true.Size() == Width(),
"PreparedMappedGravitySourceOperator received a density vector "
density.Size() == Width(), "PreparedMappedGravitySourceOperator received a density vector "
"with the wrong size."
);
mfem::Vector density_local;
m_density_true.SetSize(m_density_map.full_size());
m_density_map.scatter(density, m_density_true);
true_to_local(*m_fem.densityFes, density_true, density_local);
true_to_local(*m_fem.densityFes, m_density_true, m_density_local);
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
local_action = 0.0;
mfem::Vector element_density;
mfem::Vector quadrature_density;
mfem::Vector element_action;
m_local_action.SetSize(m_fem.gravityPotentialFes->GetVSize());
m_local_action = 0.0;
for (const ElementPAData &data : m_elements) {
density_local.GetSubVector(data.density_dofs, element_density);
m_density_local.GetSubVector(data.density_dofs, m_element_input);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(
element_density
);
data.density_dof_transformation->InvTransformPrimal(m_element_input);
}
quadrature_density.SetSize(data.quadrature_data.Size());
m_quadrature_action.SetSize(data.quadrature_data.Size());
// B_density * x_e
data.density_basis.Mult(element_density, quadrature_density);
data.density_basis.Mult(m_element_input, m_quadrature_action);
// D * B_density * x_e
for (int q = 0; q < quadrature_density.Size(); ++q) {
quadrature_density(q) *= data.quadrature_data(q);
for (int q = 0; q < m_quadrature_action.Size(); ++q) {
m_quadrature_action(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.potential_dofs.Size());
m_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(m_quadrature_action, m_element_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(
element_action
);
data.potential_dof_transformation->TransformDual(m_element_action);
}
local_action.AddElementVector(data.potential_dofs, element_action);
m_local_action.AddElementVector(data.potential_dofs, m_element_action);
}
local_to_true(*m_fem.gravityPotentialFes, local_action, action);
if (m_potential_map.is_identity()) {
local_to_true(*m_fem.gravityPotentialFes, m_local_action, action);
} else {
local_to_true(*m_fem.gravityPotentialFes, m_local_action, m_action_true);
action.SetSize(Height());
m_potential_map.gather(m_action_true, action);
}
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential_true,
mfem::Vector &action
void PreparedMappedGravitySourceOperator::MultDisplacementVariationTrue(
const mfem::Vector &densityTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const {
MFEM_VERIFY(
m_is_prepared,
"PreparedMappedGravitySourceOperator must be prepared before "
"PreparedMappedGravitySourceOperator must be prepared before applying a displacement variation."
);
MFEM_VERIFY(
m_has_variation_data,
"PreparedMappedGravitySourceOperator requires linearization preparation before applying a displacement "
"variation."
);
MFEM_VERIFY(
densityTrue.Size() == m_fem.densityFes->GetTrueVSize(), "The full density vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"The full displacement variation has the wrong size."
);
true_to_local(*m_fem.densityFes, densityTrue, m_density_local);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacement_variation_local);
m_local_variation_action.SetSize(m_fem.gravityPotentialFes->GetVSize());
m_local_variation_action = 0.0;
const int dimension = m_fem.mesh->Dimension();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(
data.integration_rule != nullptr,
"Prepared gravity source displacement variation has no integration rule."
);
m_density_local.GetSubVector(data.density_dofs, m_element_density);
m_displacement_variation_local.GetSubVector(data.displacement_dofs, m_element_displacement_variation);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->InvTransformPrimal(m_element_density);
}
if (data.displacement_dof_transformation != nullptr) {
data.displacement_dof_transformation->InvTransformPrimal(m_element_displacement_variation);
}
const mfem::FiniteElement &displacement_element = *m_fem.displacementFes->GetFE(data.element_id);
const mapping::ElementDisplacementData direction_data = mapping::ElementDisplacementDataFromElementVDofs(
displacement_element, m_element_displacement_variation
);
const mfem::DenseMatrix &direction_dofs = direction_data.GetDofMatrix();
MFEM_VERIFY(
data.inverse_element_jacobians.Height() == data.integration_rule->GetNPoints() &&
data.inverse_element_jacobians.Width() == dimension * dimension,
"Prepared gravity source inverse-Jacobian data has an incompatible size."
);
m_reference_displacement_dshape.SetSize(displacement_element.GetDof(), dimension);
m_reference_displacement_jacobian.SetSize(dimension, dimension);
m_quadrature_variation_action.SetSize(data.integration_rule->GetNPoints());
data.density_basis.Mult(m_element_density, m_quadrature_variation_action);
for (int quadrature_point = 0; quadrature_point < data.integration_rule->GetNPoints(); ++quadrature_point) {
const mfem::IntegrationPoint &integration_point = data.integration_rule->IntPoint(quadrature_point);
displacement_element.CalcDShape(integration_point, m_reference_displacement_dshape);
mfem::MultAtB(direction_dofs, m_reference_displacement_dshape, m_reference_displacement_jacobian);
double logarithmic_jacobian_variation{0.0};
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
logarithmic_jacobian_variation +=
data.inverse_element_jacobians(quadrature_point, row * dimension + column) *
m_reference_displacement_jacobian(column, row);
}
}
m_quadrature_variation_action(quadrature_point) *=
data.quadrature_data(quadrature_point) * logarithmic_jacobian_variation;
MFEM_VERIFY(
std::isfinite(m_quadrature_variation_action(quadrature_point)),
"Prepared gravity source displacement variation encountered a non-finite quadrature value."
);
}
m_element_variation_action.SetSize(data.potential_dofs.Size());
data.potential_basis.MultTranspose(m_quadrature_variation_action, m_element_variation_action);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->TransformDual(m_element_variation_action);
}
m_local_variation_action.AddElementVector(data.potential_dofs, m_element_variation_action);
}
local_to_true(*m_fem.gravityPotentialFes, m_local_variation_action, actionVariationTrue);
}
void PreparedMappedGravitySourceOperator::MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
"MultTranspose is called."
);
MFEM_VERIFY(
potential_true.Size() == Height(),
"PreparedMappedGravitySourceOperator received a potential vector "
potential.Size() == Height(), "PreparedMappedGravitySourceOperator received a potential vector "
"with the wrong size."
);
mfem::Vector potential_local;
if (m_potential_map.is_identity()) {
true_to_local(*m_fem.gravityPotentialFes, potential, m_potential_local);
} else {
m_potential_true.SetSize(m_potential_map.full_size());
m_potential_map.scatter(potential, m_potential_true);
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, m_potential_local);
}
true_to_local(
*m_fem.gravityPotentialFes, potential_true, potential_local
);
mfem::Vector local_action(m_fem.densityFes->GetVSize());
local_action = 0.0;
mfem::Vector element_potential;
mfem::Vector quadrature_potential;
mfem::Vector element_action;
m_local_action.SetSize(m_fem.densityFes->GetVSize());
m_local_action = 0.0;
for (const ElementPAData &data : m_elements) {
potential_local.GetSubVector(
data.potential_dofs, element_potential
);
m_potential_local.GetSubVector(data.potential_dofs, m_element_input);
if (data.potential_dof_transformation != nullptr) {
data.potential_dof_transformation->InvTransformPrimal(
element_potential
);
data.potential_dof_transformation->InvTransformPrimal(m_element_input);
}
quadrature_potential.SetSize(data.quadrature_data.Size());
m_quadrature_action.SetSize(data.quadrature_data.Size());
data.potential_basis.Mult(element_potential, quadrature_potential);
data.potential_basis.Mult(m_element_input, m_quadrature_action);
for (int q = 0; q < quadrature_potential.Size(); ++q) {
quadrature_potential(q) *= data.quadrature_data(q);
for (int q = 0; q < m_quadrature_action.Size(); ++q) {
m_quadrature_action(q) *= data.quadrature_data(q);
}
element_action.SetSize(data.density_dofs.Size());
m_element_action.SetSize(data.density_dofs.Size());
data.density_basis.MultTranspose(
quadrature_potential, element_action
);
data.density_basis.MultTranspose(m_quadrature_action, m_element_action);
if (data.density_dof_transformation != nullptr) {
data.density_dof_transformation->TransformDual(element_action);
data.density_dof_transformation->TransformDual(m_element_action);
}
local_action.AddElementVector(data.density_dofs, element_action);
m_local_action.AddElementVector(data.density_dofs, m_element_action);
}
local_to_true(*m_fem.densityFes, local_action, action);
local_to_true(*m_fem.densityFes, m_local_action, m_action_true);
action.SetSize(Width());
m_density_map.gather(m_action_true, action);
}
bool PreparedMappedGravitySourceOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
bool PreparedMappedGravitySourceOperator::HasVariationData() const noexcept {
return m_has_variation_data;
}
std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDensityMap() const noexcept {
return m_density_map;
}
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetPotentialMap() const noexcept {
return m_potential_map;
}
const field::FieldDofMap &PreparedMappedGravitySourceOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators

View File

@@ -1,4 +1,5 @@
module;
#include "profile.h"
#include <cmath>
#include <cstdint>
#include <memory>
@@ -8,13 +9,22 @@ module mean_field;
import :operators.prepared_hdiv_mass;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
int get_operator_size(const mean_field::fem::FEM &f) {
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
return f.gravityFluxFes->GetTrueVSize();
return mean_field::field::make_field_dof_map<mean_field::field::Gravity, DomainSchema>(*f.gravityFluxFes)
.reduced_size();
}
bool communicator_has_single_rank(const MPI_Comm communicator) {
int size = 0;
MFEM_VERIFY(MPI_Comm_size(communicator, &size) == MPI_SUCCESS, "Failed to query the MPI communicator size.");
MFEM_VERIFY(size > 0, "The MPI communicator must contain at least one rank.");
return size == 1;
}
void true_to_local(
@@ -24,8 +34,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);
@@ -34,6 +43,128 @@ namespace {
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::Vector &local_vector,
mfem::Vector &true_vector
) {
true_vector.SetSize(finite_element_space.GetTrueVSize());
true_vector = 0.0;
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(local_vector, true_vector);
} else {
true_vector = local_vector;
}
}
mean_field::quadrature::MappingKind get_mapping_kind(
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::ElementTransformation &transformation
) {
return domain_mapper.IsCompactifiedElement(transformation) ? mean_field::quadrature::MappingKind::kelvin
: mean_field::quadrature::MappingKind::general;
}
const mfem::IntegrationRule &get_hdiv_mass_rule(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::FiniteElement &element,
const mfem::ElementTransformation &transformation
) {
using GravityField = mean_field::field::Field<mean_field::field::Gravity>;
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 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."
);
return *resolution.integration_rule;
}
int frozen_mapping_width(const int dimension) {
return 3 * dimension + 4 * dimension * dimension + 3;
}
void freeze_mapping_context(
const mean_field::mapping::VolumeMappingContext &context,
const int quadrature_point,
mfem::DenseMatrix &data
) {
const int dimension = context.mapping.reference_position.Size();
const int displacement_jacobian_start = 3 * dimension;
const int mapping_jacobian_start = displacement_jacobian_start + dimension * dimension;
const int inverse_mapping_start = mapping_jacobian_start + dimension * dimension;
const int inverse_element_start = inverse_mapping_start + dimension * dimension;
const int scalar_start = inverse_element_start + dimension * dimension;
for (int component = 0; component < dimension; ++component) {
data(quadrature_point, component) = context.mapping.reference_position(component);
data(quadrature_point, dimension + component) = context.mapping.displaced_position(component);
data(quadrature_point, 2 * dimension + component) = context.mapping.physical_position(component);
}
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
data(quadrature_point, displacement_jacobian_start + entry) =
context.mapping.displacement_jacobian(row, column);
data(quadrature_point, mapping_jacobian_start + entry) = context.mapping.mapping_jacobian(row, column);
data(quadrature_point, inverse_mapping_start + entry) =
context.mapping.inverse_mapping_jacobian(row, column);
data(quadrature_point, inverse_element_start + entry) = context.quadrature.J_inv(row, column);
}
}
data(quadrature_point, scalar_start) = context.mapping.mapping_determinant;
data(quadrature_point, scalar_start + 1) = context.quadrature.weight;
data(quadrature_point, scalar_start + 2) = context.mapping.compactified ? 1.0 : 0.0;
}
void thaw_mapping_context(
const mfem::DenseMatrix &data,
const int quadrature_point,
const int dimension,
mean_field::mapping::VolumeMappingContext &context
) {
const int displacement_jacobian_start = 3 * dimension;
const int mapping_jacobian_start = displacement_jacobian_start + dimension * dimension;
const int inverse_mapping_start = mapping_jacobian_start + dimension * dimension;
const int inverse_element_start = inverse_mapping_start + dimension * dimension;
const int scalar_start = inverse_element_start + dimension * dimension;
context.mapping.reference_position.SetSize(dimension);
context.mapping.displaced_position.SetSize(dimension);
context.mapping.physical_position.SetSize(dimension);
context.mapping.displacement_jacobian.SetSize(dimension, dimension);
context.mapping.mapping_jacobian.SetSize(dimension, dimension);
context.mapping.inverse_mapping_jacobian.SetSize(dimension, dimension);
context.quadrature.J_inv.SetSize(dimension, dimension);
for (int component = 0; component < dimension; ++component) {
context.mapping.reference_position(component) = data(quadrature_point, component);
context.mapping.displaced_position(component) = data(quadrature_point, dimension + component);
context.mapping.physical_position(component) = data(quadrature_point, 2 * dimension + component);
}
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
const int entry = row * dimension + column;
context.mapping.displacement_jacobian(row, column) =
data(quadrature_point, displacement_jacobian_start + entry);
context.mapping.mapping_jacobian(row, column) = data(quadrature_point, mapping_jacobian_start + entry);
context.mapping.inverse_mapping_jacobian(row, column) =
data(quadrature_point, inverse_mapping_start + entry);
context.quadrature.J_inv(row, column) = data(quadrature_point, inverse_element_start + entry);
}
}
context.mapping.mapping_determinant = data(quadrature_point, scalar_start);
context.mapping.compactified = data(quadrature_point, scalar_start + 2) != 0.0;
context.quadrature.detJ = context.mapping.mapping_determinant;
context.quadrature.weight = data(quadrature_point, scalar_start + 1);
}
int find_representative_element(
const mean_field::fem::FEM &f,
const mfem::Array<int> &marker
@@ -41,8 +172,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 +185,19 @@ namespace {
const mfem::Array<int> &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,20 +211,18 @@ 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,
const mean_field::mapping::DomainMapperStateless &domain_mapper,
const mean_field::mapping::DomainMapper &domain_mapper,
const mfem::Vector &displacement_true,
bool elevates_vacuum
)
@@ -107,9 +231,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(
@@ -126,8 +248,9 @@ namespace {
);
const bool element_is_vacuum =
transformation.Attribute ==
m_domain_mapper.GetVacuumElementAttribute();
DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(
transformation.Attribute
);
if (element_is_vacuum != m_elevates_vacuum) {
mass_tensor.SetSize(m_domain_mapper.GetDimension());
@@ -138,16 +261,13 @@ 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(
@@ -155,17 +275,13 @@ namespace {
"Stateless domain mapping failed while preparing the H(div) "
"mass "
"operator. Mapping status = "
<< static_cast<int>(status)
<< ", element ID = " << element_id
<< static_cast<int>(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 +299,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::ElementDisplacementData>(
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<mean_field::mapping::ElementCompactificationData>(
compactification_element, m_element_compactification
);
@@ -232,7 +332,7 @@ namespace {
}
const mean_field::fem::FEM &m_fem;
const mean_field::mapping::DomainMapperStateless &m_domain_mapper;
const mean_field::mapping::DomainMapper &m_domain_mapper;
mfem::Vector m_displacement_local;
@@ -242,12 +342,10 @@ namespace {
mfem::Vector m_element_displacement;
mfem::Vector m_element_compactification;
std::unique_ptr<mean_field::mapping::ElementDisplacementData>
m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData>
m_compactification_data;
std::unique_ptr<mean_field::mapping::ElementDisplacementData> m_displacement_data;
std::unique_ptr<mean_field::mapping::ElementCompactificationData> m_compactification_data;
mean_field::mapping::DomainMapperStateless::Workspace m_workspace;
mean_field::mapping::DomainMapper::Workspace m_workspace;
int m_cached_element_id{-1};
bool m_elevates_vacuum;
};
@@ -256,37 +354,42 @@ namespace {
namespace mean_field::operators {
PreparedMappedHDivMassOperator::PreparedMappedHDivMassOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domain_mapper
const mapping::DomainMapper &domain_mapper
)
: Operator(get_operator_size(f)),
m_fem(f),
m_domain_mapper(domain_mapper) {
m_domain_mapper(domain_mapper),
m_flux_map(
field::make_field_dof_map<
field::Gravity,
DomainSchema>(*f.gravityFluxFes)
),
m_displacement_map(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
),
m_variationWorkspace(domain_mapper.GetDimension()),
m_single_rank(communicator_has_single_rank(f.gravityFluxFes->GetComm())) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh.");
MFEM_VERIFY(
f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"gravity-gradient finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"PreparedMappedHDivMassOperator requires the "
f.displacementFes != nullptr, "PreparedMappedHDivMassOperator requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"PreparedMappedHDivMassOperator requires the compactification "
f.compactificationFes != nullptr, "PreparedMappedHDivMassOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"PreparedMappedHDivMassOperator requires the compactification "
f.compactificationCoordinate != nullptr, "PreparedMappedHDivMassOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"PreparedMappedHDivMassOperator requires the quadrature-rule "
f.quadratureFactory != nullptr, "PreparedMappedHDivMassOperator requires the quadrature-rule "
"factory."
);
MFEM_VERIFY(
@@ -295,147 +398,391 @@ 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
);
m_stellar_marker = utils::domain::make_attribute_marker<utils::domain::Stellar, DomainSchema>(*f.mesh);
m_vacuum_marker = utils::domain::make_attribute_marker<utils::domain::Vacuum, DomainSchema>(*f.mesh);
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 "
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::PrepareVariationData() {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::PrepareVariationData", 0);
m_variationElements.clear();
m_variationElements.reserve(m_fem.mesh->GetNE());
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, m_displacement_true, displacementLocal);
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mapping::VolumeMappingContext mappingContext;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
m_variationElements.emplace_back();
ElementVariationData &data = m_variationElements.back();
data.elementId = elementId;
data.gravityGradientDofTransformation =
m_fem.gravityFluxFes->GetElementVDofs(elementId, data.gravityGradientDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, elementCompactification);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
data.baseDisplacement = elementDisplacement;
data.compactification = elementCompactification;
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr, "Prepared H(div) variation data received a null element transformation."
);
data.integrationRule = &get_hdiv_mass_rule(m_fem, m_domain_mapper, gravityGradientElement, *transformation);
data.frozenMappingData.SetSize(
data.integrationRule->GetNPoints(), frozen_mapping_width(m_domain_mapper.GetDimension())
);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, m_variationWorkspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Prepared H(div) variation data encountered an invalid mapping. Element: "
<< elementId << ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(status)
);
freeze_mapping_context(mappingContext, quadraturePoint, data.frozenMappingData);
}
}
}
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare linearization", 0);
PrepareImpl(displacement, PreparationMode::linearization);
}
void PreparedMappedHDivMassOperator::PreparePrimal(const mfem::Vector &displacement) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare primal", 0);
PrepareImpl(displacement, PreparationMode::primal);
}
void PreparedMappedHDivMassOperator::PrepareImpl(
const mfem::Vector &displacement,
const PreparationMode mode
) {
MFEM_VERIFY(
displacement_true.Size() == m_fem.displacementFes->GetTrueVSize(),
displacement.Size() == m_displacement_map.reduced_size(),
"PreparedMappedHDivMassOperator received a displacement vector "
"with "
"the wrong size."
);
for (int i = 0; i < displacement_true.Size(); ++i) {
for (int i = 0; i < displacement.Size(); ++i) {
MFEM_VERIFY(
std::isfinite(displacement_true(i)),
"PreparedMappedHDivMassOperator received a non-finite "
std::isfinite(displacement(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);
m_is_prepared = false;
m_has_variation_data = false;
const mfem::FiniteElement &stellar_element =
*m_fem.gravityFluxFes->GetFE(stellar_element_id);
const mfem::FiniteElement &vacuum_element =
*m_fem.gravityFluxFes->GetFE(vacuum_element_id);
m_displacement_true.SetSize(m_displacement_map.full_size());
m_displacement_map.scatter(displacement, m_displacement_true);
mfem::ElementTransformation &stellar_transformation =
*m_fem.mesh->GetElementTransformation(stellar_element_id);
mfem::ElementTransformation &vacuum_transformation =
*m_fem.mesh->GetElementTransformation(vacuum_element_id);
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);
m_mass_form.reset();
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);
m_stellar_mass_form.reset();
m_vacuum_mass_form.reset();
m_stellar_mass_coefficient.reset();
m_vacuum_mass_coefficient.reset();
m_stellar_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, displacement_true, false
);
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, false);
m_vacuum_mass_coefficient =
std::make_unique<FrozenMappedHDivMassCoefficient>(
m_fem, m_domain_mapper, displacement_true, true
);
std::make_unique<FrozenMappedHDivMassCoefficient>(m_fem, m_domain_mapper, m_displacement_true, true);
m_mass_form =
std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_stellar_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_vacuum_mass_form = std::make_unique<mfem::ParBilinearForm>(m_fem.gravityFluxFes.get());
m_stellar_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
m_vacuum_mass_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto stellar_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>(
*m_stellar_mass_coefficient
);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(
*m_vacuum_mass_coefficient
auto stellar_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*m_stellar_mass_coefficient);
auto vacuum_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*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_stellar_mass_form->AddDomainIntegrator(stellar_integrator.release(), m_stellar_marker);
m_vacuum_mass_form->AddDomainIntegrator(vacuum_integrator.release(), m_vacuum_marker);
m_stellar_mass_form->Assemble();
m_vacuum_mass_form->Assemble();
m_mass_form->AddDomainIntegrator(
stellar_integrator.release(), m_stellar_marker
);
m_mass_form->AddDomainIntegrator(
vacuum_integrator.release(), m_vacuum_marker
);
m_mass_form->Assemble();
if (mode == PreparationMode::linearization) {
PrepareVariationData();
m_has_variation_data = true;
} else {
m_variationElements.clear();
}
m_is_prepared = true;
++m_preparation_count;
}
void PreparedMappedHDivMassOperator::Mult(
const mfem::Vector &gravity_gradient_true,
const mfem::Vector &gravity_gradient,
mfem::Vector &action
) const {
MEAN_FIELD_PROFILE_SCOPE("PreparedMappedHDivMassOperator::Mult");
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before Mult is called."
);
MFEM_VERIFY(
m_mass_form != nullptr, "PreparedMappedHDivMassOperator has no "
"assembled partial-assembly form."
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
MFEM_VERIFY(
gravity_gradient_true.Size() == Width(),
"PreparedMappedHDivMassOperator received a gravity-gradient vector "
gravity_gradient.Size() == Width(), "PreparedMappedHDivMassOperator received a gravity-gradient vector "
"with the wrong size."
);
const mfem::Vector *gravity_gradient_true = &gravity_gradient;
if (!m_flux_map.is_identity()) [[unlikely]] {
m_flux_true.SetSize(m_flux_map.full_size());
m_flux_map.scatter(gravity_gradient, m_flux_true);
gravity_gradient_true = &m_flux_true;
}
mfem::Vector *action_true = &action;
if (!m_flux_map.is_identity()) [[unlikely]] {
m_action_true.SetSize(m_flux_map.full_size());
action_true = &m_action_true;
}
if (m_single_rank) [[likely]] {
action_true->SetSize(m_flux_map.full_size());
m_domain_action_true.SetSize(m_flux_map.full_size());
m_stellar_mass_form->Mult(*gravity_gradient_true, *action_true);
m_vacuum_mass_form->Mult(*gravity_gradient_true, m_domain_action_true);
*action_true += m_domain_action_true;
} else {
true_to_local(*m_fem.gravityFluxFes, *gravity_gradient_true, m_flux_local);
m_action_local.SetSize(m_fem.gravityFluxFes->GetVSize());
m_domain_action_local.SetSize(m_fem.gravityFluxFes->GetVSize());
m_stellar_mass_form->Mult(m_flux_local, m_action_local);
m_vacuum_mass_form->Mult(m_flux_local, m_domain_action_local);
m_action_local += m_domain_action_local;
local_to_true(*m_fem.gravityFluxFes, m_action_local, *action_true);
}
if (!m_flux_map.is_identity()) [[unlikely]] {
action.SetSize(Height());
m_mass_form->Mult(gravity_gradient_true, action);
m_flux_map.gather(m_action_true, action);
}
}
void PreparedMappedHDivMassOperator::MultDisplacementVariationTrue(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared before applying a displacement variation."
);
MFEM_VERIFY(
m_has_variation_data,
"PreparedMappedHDivMassOperator requires linearization preparation before applying a displacement "
"variation."
);
MFEM_VERIFY(
gravityGradientTrue.Size() == m_fem.gravityFluxFes->GetTrueVSize(),
"The full gravity-gradient vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() == m_fem.displacementFes->GetTrueVSize(),
"The full displacement variation has the wrong size."
);
true_to_local(*m_fem.gravityFluxFes, gravityGradientTrue, m_gravityGradientLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localVariationAction.SetSize(m_fem.gravityFluxFes->GetVSize());
m_localVariationAction = 0.0;
const int dimension = m_domain_mapper.GetDimension();
for (const ElementVariationData &data : m_variationElements) {
MFEM_VERIFY(
data.integrationRule != nullptr &&
data.frozenMappingData.Height() == data.integrationRule->GetNPoints() &&
data.frozenMappingData.Width() == frozen_mapping_width(dimension),
"Prepared H(div) variation data is incomplete."
);
m_gravityGradientLocal.GetSubVector(data.gravityGradientDofs, m_elementGravityGradient);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->InvTransformPrimal(m_elementGravityGradient);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &gravityGradientElement = *m_fem.gravityFluxFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(
transformation != nullptr,
"Prepared H(div) displacement variation received a null element transformation."
);
const mapping::ElementDisplacementData baseDisplacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData, .compactification = compactificationData
};
m_elementVariationAction.SetSize(gravityGradientElement.GetDof());
m_elementVariationAction = 0.0;
m_gravityGradientValue.SetSize(dimension);
m_massTensorVariationAction.SetSize(dimension);
m_gravityGradientShape.SetSize(gravityGradientElement.GetDof(), dimension);
m_massTensorVariation.SetSize(dimension, dimension);
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
thaw_mapping_context(data.frozenMappingData, quadraturePoint, dimension, m_baseMappingContext);
const mapping::MappingStatus status = m_domain_mapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, integrationPoint, m_baseMappingContext,
m_variationWorkspace, m_mappingVariation
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Prepared H(div) displacement variation encountered an invalid mapping variation. Element: "
<< data.elementId << ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(status)
);
mapping::ComputeHDivMassTensorVariation(
m_baseMappingContext.mapping, m_mappingVariation.mapping, m_massTensorVariation
);
transformation->SetIntPoint(&integrationPoint);
gravityGradientElement.CalcVShape(*transformation, m_gravityGradientShape);
m_gravityGradientShape.MultTranspose(m_elementGravityGradient, m_gravityGradientValue);
m_massTensorVariation.Mult(m_gravityGradientValue, m_massTensorVariationAction);
const double referenceWeight = integrationPoint.weight * transformation->Weight();
m_gravityGradientShape.AddMult(m_massTensorVariationAction, m_elementVariationAction, referenceWeight);
}
if (data.gravityGradientDofTransformation != nullptr) {
data.gravityGradientDofTransformation->TransformDual(m_elementVariationAction);
}
m_localVariationAction.AddElementVector(data.gravityGradientDofs, m_elementVariationAction);
}
local_to_true(*m_fem.gravityFluxFes, m_localVariationAction, actionVariationTrue);
}
void PreparedMappedHDivMassOperator::AssembleDiagonal(mfem::Vector &diagonal) const {
mfem::Vector true_diagonal;
AssembleTrueDiagonal(true_diagonal);
diagonal.SetSize(Height());
m_flux_map.gather(true_diagonal, diagonal);
}
void PreparedMappedHDivMassOperator::AssembleTrueDiagonal(mfem::Vector &diagonal) const {
MFEM_VERIFY(
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
"before assembling its diagonal."
);
MFEM_VERIFY(
m_stellar_mass_form != nullptr && m_vacuum_mass_form != nullptr,
"PreparedMappedHDivMassOperator has incomplete domain mass forms."
);
diagonal.SetSize(m_flux_map.full_size());
mfem::Vector domain_diagonal(m_flux_map.full_size());
m_stellar_mass_form->AssembleDiagonal(diagonal);
m_vacuum_mass_form->AssembleDiagonal(domain_diagonal);
diagonal += domain_diagonal;
}
bool PreparedMappedHDivMassOperator::IsPrepared() const noexcept {
return m_is_prepared;
}
std::uint64_t
PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
bool PreparedMappedHDivMassOperator::HasVariationData() const noexcept {
return m_has_variation_data;
}
std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
return m_preparation_count;
}
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetFluxMap() const noexcept {
return m_flux_map;
}
const field::FieldDofMap &PreparedMappedHDivMassOperator::GetDisplacementMap() const noexcept {
return m_displacement_map;
}
} // namespace mean_field::operators

View File

@@ -0,0 +1,920 @@
module;
#include <array>
#include <cmath>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_mass_normalization;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
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;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(localVector.Size() == finiteElementSpace.GetVSize(), "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;
}
}
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<mean_field::field::Density>;
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::field::Density::Form::MassNormalization>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 0>{},
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::DomainMapper &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."
);
MFEM_VERIFY(
m_gravityContext.GetDensityMap().full_size() == m_fem.densityFes->GetTrueVSize() &&
m_gravityContext.GetDisplacementMap().full_size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedMassNormalizationOperator received incompatible shared "
"FieldDof maps."
);
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
}
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().GetDisplacementTrue());
report.refreshedGeometry = true;
}
if (refreshDensity) {
RefreshDensity(m_gravityContext.GetDensityTrue());
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;
}
PreparedMassNormalizationReport PreparedMassNormalizationOperator::Prepare(
const models::CompiledFixedMass &constraint,
const MassNormalizationDependencies &dependencies
) {
return Prepare({.targetMass = constraint.targetMass().value()}, dependencies);
}
void PreparedMassNormalizationOperator::BuildStaticPlan() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
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 (is_vacuum_attribute(transformation->Attribute)) {
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::DomainMapper::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<int>(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::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
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) {
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<int>(status)
);
localAction += point.density * variation.weight_variation;
}
}
return localAction;
}
void PreparedMassNormalizationOperator::ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Mass-normalization density action received a supported vector "
"with the wrong size."
);
validate_finite_vector(densityVariation, "Mass-normalization density action received a non-finite value.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
action.SetSize(1);
action(0) = GlobalSum(EvaluateDensityActionLocal(m_densityVariationTrue));
++m_actionStatistics.densityApplications;
}
void PreparedMassNormalizationOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Mass-normalization displacement action received a supported "
"vector with the wrong size."
);
validate_finite_vector(
displacementVariation, "Mass-normalization displacement action received a non-finite value."
);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
action.SetSize(1);
action(0) = GlobalSum(EvaluateDisplacementActionLocal(m_displacementVariationTrue));
++m_actionStatistics.displacementApplications;
}
void PreparedMassNormalizationOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Mass-normalization complete action received a supported density "
"vector with the wrong size."
);
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Mass-normalization complete action received a supported "
"displacement vector with the wrong size."
);
validate_finite_vector(densityVariation, "Mass-normalization complete action received a non-finite density.");
validate_finite_vector(
displacementVariation, "Mass-normalization complete action received a non-finite displacement."
);
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
const double localAction = EvaluateDensityActionLocal(m_densityVariationTrue) +
EvaluateDisplacementActionLocal(m_displacementVariationTrue);
action.SetSize(1);
action(0) = GlobalSum(localAction);
++m_actionStatistics.completeApplications;
}
void PreparedMassNormalizationOperator::ApplyJacobian(
const FixedMassJacobianInput &input,
mfem::Vector &action
) const {
ApplyCompleteJacobianAction(input.densityVariation, input.displacementVariation, action);
}
void PreparedMassNormalizationOperator::AssembleDensityTransposeAction(
const double residualDual,
mfem::Vector &densityDual
) const {
mfem::Vector localDual(m_fem.densityFes->GetVSize());
localDual = 0.0;
mfem::Vector elementDual;
for (const ElementPAData &data : m_elements) {
elementDual.SetSize(data.densityDofs.Size());
elementDual = 0.0;
for (const QuadraturePointData &point : data.quadraturePoints) {
elementDual.Add(residualDual * point.mappingContext.quadrature.weight, point.densityShape);
}
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->TransformDual(elementDual);
}
localDual.AddElementVector(data.densityDofs, elementDual);
}
mfem::Vector trueDual;
local_to_true(*m_fem.densityFes, localDual, trueDual);
densityDual.SetSize(m_gravityContext.GetDensityMap().reduced_size());
m_gravityContext.GetDensityMap().gather(trueDual, densityDual);
}
void PreparedMassNormalizationOperator::AssembleDisplacementTransposeAction(
const double residualDual,
mfem::Vector &displacementDual
) const {
mfem::Vector localDual(m_fem.displacementFes->GetVSize());
localDual = 0.0;
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
mfem::Vector elementDirection;
mfem::Vector elementDual;
for (const ElementPAData &data : m_elements) {
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::ElementCompactificationData compactificationData(
compactificationElement, data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData, .compactification = compactificationData
};
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
elementDirection.SetSize(data.displacementDofs.Size());
elementDual.SetSize(data.displacementDofs.Size());
elementDual = 0.0;
for (int elementDof = 0; elementDof < elementDirection.Size(); ++elementDof) {
elementDirection = 0.0;
elementDirection(elementDof) = 1.0;
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDirection);
double elementDofAction = 0.0;
for (const QuadraturePointData &point : data.quadraturePoints) {
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 transpose action. Element: "
<< data.elementId << ", status: " << static_cast<int>(status)
);
elementDofAction += point.density * variation.weight_variation;
}
elementDual(elementDof) = residualDual * elementDofAction;
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(elementDual);
}
localDual.AddElementVector(data.displacementDofs, elementDual);
}
mfem::Vector trueDual;
local_to_true(*m_fem.displacementFes, localDual, trueDual);
displacementDual.SetSize(m_gravityContext.GetDisplacementMap().reduced_size());
m_gravityContext.GetDisplacementMap().gather(trueDual, displacementDual);
}
void PreparedMassNormalizationOperator::ApplyCompleteJacobianTransposeAction(
const double residualDual,
mfem::Vector &densityDual,
mfem::Vector &displacementDual
) const {
VerifyPrepared();
MFEM_VERIFY(std::isfinite(residualDual), "Mass-normalization transpose action received a non-finite dual.");
AssembleDensityTransposeAction(residualDual, densityDual);
AssembleDisplacementTransposeAction(residualDual, displacementDual);
++m_actionStatistics.transposeApplications;
}
void PreparedMassNormalizationOperator::ApplyJacobianTranspose(
const mfem::Vector &residualDual,
FixedMassJacobianTransposeOutput output
) const {
MFEM_VERIFY(residualDual.Size() == 1, "Fixed-mass transpose action requires one residual dual value.");
ApplyCompleteJacobianTransposeAction(residualDual(0), output.densityDual, output.displacementDual);
}
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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto gravityGradientValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialValue =
utils::blocks::get_value_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto barotropicConstantValue =
utils::blocks::get_value_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto gravityGradientResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravityPotentialResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::gravity_field.poisson_term);
constexpr auto densityResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const auto &gravityContext = m_preparedOperator.GetGravityContext();
const field::FieldDofMap enthalpyMap = field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
MFEM_VERIFY(
m_layout.size(densityValue) == gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) == gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(gravityGradientValue) == gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialValue) == gravityContext.GetGravityPotentialMap().reduced_size() &&
m_layout.size(enthalpyValue) == enthalpyMap.reduced_size() &&
m_layout.size(barotropicConstantValue) == 1 &&
m_layout.size(gravityGradientResidual) == gravityContext.GetGravityGradientMap().reduced_size() &&
m_layout.size(gravityPotentialResidual) == gravityContext.GetGravityPotentialMap().reduced_size() &&
m_layout.size(densityResidual) == gravityContext.GetDensityMap().reduced_size() &&
m_layout.size(displacementResidual) == gravityContext.GetDisplacementMap().reduced_size() &&
m_layout.size(enthalpyResidual) == enthalpyMap.reduced_size() && 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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::barotropic_constant_field.mass_normalization_term);
const mfem::Vector densityVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue)
);
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(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);
}
void PreparedMassNormalizationJacobianOperator::MultTranspose(
const mfem::Vector &residualDual,
mfem::Vector &stateDual
) const {
MFEM_VERIFY(
m_preparedOperator.IsPrepared(),
"Prepared mass-normalization MFEM adapter requires a prepared row operator."
);
MFEM_VERIFY(
residualDual.Size() == Height(),
"Prepared mass-normalization MFEM adapter received a residual dual with the wrong size."
);
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto massResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
mfem::Vector densityDual;
mfem::Vector displacementDual;
m_preparedOperator.ApplyCompleteJacobianTransposeAction(
residualDual(m_layout.offset(massResidual)), densityDual, displacementDual
);
stateDual.SetSize(Width());
stateDual = 0.0;
mfem::Vector densityBlock(stateDual.GetData() + m_layout.offset(densityValue), m_layout.size(densityValue));
densityBlock = densityDual;
mfem::Vector displacementBlock(
stateDual.GetData() + m_layout.offset(displacementValue), m_layout.size(displacementValue)
);
displacementBlock = displacementDual;
}
const MassNormalizationLayout &PreparedMassNormalizationJacobianOperator::GetLayout() const noexcept {
return m_layout;
}
} // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,565 @@
module;
#include <array>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.rotational_displacement_force;
import :operators.prepared_rotational_displacement_force;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
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
) {
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;
}
MFEM_VERIFY(ordering == mfem::Ordering::byVDIM, "Unsupported displacement ordering.");
return scalarDof * dimension + component;
}
[[nodiscard]] const mfem::IntegrationRule &get_rotation_force_rule(
const mean_field::fem::FEM &f,
const mfem::ElementTransformation &transformation
) {
using DisplacementField = mean_field::field::Field<mean_field::field::Displacement>;
const mean_field::quadrature::Query query =
DisplacementField::make_query<mean_field::field::Displacement::Form::CentrifugalForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(), std::array<int, 1>{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;
}
} // namespace
namespace mean_field::operators {
PreparedRotationalDisplacementForceOperator::PreparedRotationalDisplacementForceOperator(
const fem::FEM &f,
const mapping::DomainMapper &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."
);
}
void PreparedRotationalDisplacementForceOperator::PrepareElementData() {
MFEM_VERIFY(m_rotation.has_value(), "Prepared rotational force has no frozen rotation state.");
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector baseDensityLocal;
mfem::Vector baseDisplacementLocal;
true_to_local(*m_fem.densityFes, m_context.GetBaseDensityTrue(), baseDensityLocal);
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), baseDisplacementLocal);
mapping::DomainMapper::Workspace workspace(m_domainMapper.GetDimension());
mapping::VolumeMappingContext mappingContext;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseDisplacement;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector potentialGradient;
const int dimension = m_domainMapper.GetDimension();
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr, "Prepared rotational force received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
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);
mfem::DofTransformation *compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, compactificationDofs);
baseDensityLocal.GetSubVector(data.densityDofs, elementBaseDensity);
baseDisplacementLocal.GetSubVector(data.displacementDofs, elementBaseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(compactificationDofs, elementCompactification);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(elementBaseDensity);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementBaseDisplacement);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
data.integrationRule = &get_rotation_force_rule(m_fem, *transformation);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementBaseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData, .compactification = compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
data.inverseElementJacobians.SetSize(quadraturePointCount, dimension * dimension);
data.centrifugalAccelerations.SetSize(quadraturePointCount, dimension);
data.baseDensityValues.SetSize(quadraturePointCount);
data.quadratureWeights.SetSize(quadraturePointCount);
densityShape.SetSize(densityElement.GetDof());
potentialGradient.SetSize(dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid && !mappingContext.mapping.compactified,
"Prepared rotational force encountered an invalid stellar mapping."
);
densityElement.CalcShape(integrationPoint, densityShape);
m_rotation->potential_gradient(mappingContext.mapping.physical_position, potentialGradient);
data.baseDensityValues(quadraturePoint) = elementBaseDensity * densityShape;
data.quadratureWeights(quadraturePoint) = mappingContext.quadrature.weight;
for (int row = 0; row < dimension; ++row) {
data.centrifugalAccelerations(quadraturePoint, row) = -potentialGradient(row);
for (int column = 0; column < dimension; ++column) {
data.inverseElementJacobians(quadraturePoint, row * dimension + column) =
mappingContext.quadrature.J_inv(row, column);
}
}
}
}
}
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_actionTrue
);
m_cachedResidual.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, m_cachedResidual);
PrepareElementData();
++m_residualPreparationCount;
report.preparedResidual = true;
}
MFEM_VERIFY(
m_cachedResidual.Size() == m_context.GetDisplacementMap().reduced_size(),
"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();
m_densityVariationTrue.SetSize(m_context.GetDensityMap().full_size());
m_context.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
kernels::apply_rotational_displacement_force_density_action(
m_fem, m_domainMapper, *m_rotation, m_densityVariationTrue, m_context.GetDisplacementTrue(), m_actionTrue
);
action.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, action);
++m_densityJacobianStatistics.applications;
}
void PreparedRotationalDisplacementForceOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
m_displacementVariationTrue.SetSize(m_context.GetDisplacementMap().full_size());
m_context.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
kernels::apply_rotational_displacement_force_displacement_action(
m_fem, m_domainMapper, *m_rotation, m_context.GetBaseDensityTrue(), m_displacementVariationTrue,
m_context.GetDisplacementTrue(), m_actionTrue
);
action.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, action);
++m_displacementJacobianStatistics.applications;
}
void PreparedRotationalDisplacementForceOperator::ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const {
true_to_local(*m_fem.densityFes, densityVariationTrue, m_densityVariationLocal);
true_to_local(*m_fem.displacementFes, displacementVariationTrue, m_displacementVariationLocal);
m_localAction.SetSize(m_fem.displacementFes->GetVSize());
m_localAction = 0.0;
const int dimension = m_domainMapper.GetDimension();
const mfem::Ordering::Type ordering = m_fem.displacementFes->GetOrdering();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared rotational force has no integration rule.");
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(data.elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mfem::DenseMatrix &directionDofs = directionData.GetDofMatrix();
const int scalarDisplacementDofCount = displacementElement.GetDof();
m_densityShape.SetSize(densityElement.GetDof());
m_displacementShape.SetSize(scalarDisplacementDofCount);
m_referenceDisplacementDShape.SetSize(scalarDisplacementDofCount, dimension);
m_referenceDisplacementJacobian.SetSize(dimension, dimension);
m_physicalPositionVariation.SetSize(dimension);
m_centrifugalAcceleration.SetSize(dimension);
m_centrifugalAccelerationVariation.SetSize(dimension);
m_weightedForce.SetSize(dimension);
m_elementAction.SetSize(data.displacementDofs.Size());
m_elementAction = 0.0;
for (int quadraturePoint = 0; quadraturePoint < data.integrationRule->GetNPoints(); ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
densityElement.CalcShape(integrationPoint, m_densityShape);
displacementElement.CalcShape(integrationPoint, m_displacementShape);
displacementElement.CalcDShape(integrationPoint, m_referenceDisplacementDShape);
mfem::MultAtB(directionDofs, m_referenceDisplacementDShape, m_referenceDisplacementJacobian);
directionDofs.MultTranspose(m_displacementShape, m_physicalPositionVariation);
m_rotation->potential_gradient_directional_derivative(
m_physicalPositionVariation, m_centrifugalAccelerationVariation
);
m_centrifugalAccelerationVariation *= -1.0;
double logarithmicJacobianVariation{0.0};
for (int row = 0; row < dimension; ++row) {
m_centrifugalAcceleration(row) = data.centrifugalAccelerations(quadraturePoint, row);
for (int column = 0; column < dimension; ++column) {
logarithmicJacobianVariation +=
data.inverseElementJacobians(quadraturePoint, row * dimension + column) *
m_referenceDisplacementJacobian(column, row);
}
}
const double densityVariationValue = m_elementDensityVariation * m_densityShape;
const double baseDensityValue = data.baseDensityValues(quadraturePoint);
m_weightedForce = 0.0;
m_weightedForce.Add(densityVariationValue, m_centrifugalAcceleration);
m_weightedForce.Add(baseDensityValue, m_centrifugalAccelerationVariation);
m_weightedForce.Add(baseDensityValue * logarithmicJacobianVariation, m_centrifugalAcceleration);
m_weightedForce *= data.quadratureWeights(quadraturePoint);
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof =
vector_dof_index(ordering, scalarDof, component, scalarDisplacementDofCount, dimension);
m_elementAction(vectorDof) += m_displacementShape(scalarDof) * m_weightedForce(component);
}
}
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(m_elementAction);
}
m_localAction.AddElementVector(data.displacementDofs, m_elementAction);
}
local_to_true(*m_fem.displacementFes, m_localAction, actionTrue);
}
void PreparedRotationalDisplacementForceOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
m_densityVariationTrue.SetSize(m_context.GetDensityMap().full_size());
m_displacementVariationTrue.SetSize(m_context.GetDisplacementMap().full_size());
m_context.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_context.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
ApplyPreparedCompleteJacobianActionTrue(m_densityVariationTrue, m_displacementVariationTrue, m_actionTrue);
action.SetSize(m_context.GetDisplacementMap().reduced_size());
m_context.GetDisplacementMap().gather(m_actionTrue, 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) {
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto densityValue = utils::blocks::get_value_block<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
MFEM_VERIFY(
m_layout.size(densityValue) == m_preparedOperator.GetContext().GetDensityMap().reduced_size() &&
m_layout.size(displacementValue) ==
m_preparedOperator.GetContext().GetDisplacementMap().reduced_size() &&
m_layout.size(displacementResidual) ==
m_preparedOperator.GetContext().GetDisplacementMap().reduced_size(),
"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<Form>(utils::blocks::density_field.mass_term);
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
const mfem::Vector densityVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(densityValue), m_layout.size(densityValue)
);
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(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

View File

@@ -0,0 +1,873 @@
module;
#include <array>
#include <cmath>
#include <cstdint>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_stellar_equilibrium;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
void verify_coupled_discretization(const 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."
);
}
using StellarRootForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
[[nodiscard]] std::array<
int,
StellarRootForm::value_block_count>
make_value_sizes(
const mean_field::field::FieldDofMap &densityMap,
const int surfaceDeformationParameterCount,
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap,
const mean_field::field::FieldDofMap &enthalpyMap
) {
return {densityMap.reduced_size(), surfaceDeformationParameterCount, gravityFluxMap.reduced_size(),
gravityPotentialMap.reduced_size(), enthalpyMap.reduced_size(), 1};
}
[[nodiscard]] std::array<
int,
StellarRootForm::residual_block_count>
make_residual_sizes(
const mean_field::field::FieldDofMap &densityMap,
const int surfaceDeformationParameterCount,
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap,
const mean_field::field::FieldDofMap &enthalpyMap
) {
return {gravityFluxMap.reduced_size(), gravityPotentialMap.reduced_size(), densityMap.reduced_size(),
surfaceDeformationParameterCount, enthalpyMap.reduced_size(), 1};
}
[[nodiscard]] mfem::Array<int> make_gravity_state_offsets(
const mean_field::field::FieldDofMap &densityMap,
const mean_field::field::FieldDofMap &displacementMap,
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap
) {
mfem::Array<int> offsets(5);
offsets[0] = 0;
offsets[1] = offsets[0] + densityMap.reduced_size();
offsets[2] = offsets[1] + displacementMap.reduced_size();
offsets[3] = offsets[2] + gravityFluxMap.reduced_size();
offsets[4] = offsets[3] + gravityPotentialMap.reduced_size();
return offsets;
}
[[nodiscard]] mfem::Array<int> make_gravity_residual_offsets(
const mean_field::field::FieldDofMap &gravityFluxMap,
const mean_field::field::FieldDofMap &gravityPotentialMap
) {
mfem::Array<int> offsets(3);
offsets[0] = 0;
offsets[1] = gravityFluxMap.reduced_size();
offsets[2] = offsets[1] + gravityPotentialMap.reduced_size();
return offsets;
}
void assign_gravity_block(
mfem::Vector &gravityState,
const mfem::Array<int> &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<int> &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,
const mean_field::operators::StellarEquilibriumDependencyStamp &generatedDisplacement
) {
return {
.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = generatedDisplacement.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,
const mean_field::operators::StellarEquilibriumDependencyStamp &generatedDisplacement
) {
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 = generatedDisplacement.identity, .revision = generatedDisplacement.revision}
};
}
[[nodiscard]] mean_field::operators::DisplacementResidualDependencies make_displacement_dependencies(
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
const mean_field::operators::StellarEquilibriumDependencyStamp &generatedDisplacement
) {
return {
.discretization =
{.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision},
.density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision},
.displacement = {.identity = generatedDisplacement.identity, .revision = generatedDisplacement.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,
const mean_field::operators::StellarEquilibriumDependencyStamp &generatedDisplacement
) {
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 = generatedDisplacement.identity, .revision = generatedDisplacement.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,
const mean_field::operators::StellarEquilibriumDependencyStamp &generatedDisplacement
) {
return {
.discretization =
{.identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision},
.density = {.identity = dependencies.density.identity, .revision = dependencies.density.revision},
.displacement = {.identity = generatedDisplacement.identity, .revision = generatedDisplacement.revision},
.targetMass = {.identity = dependencies.targetMass.identity, .revision = dependencies.targetMass.revision}
};
}
} // namespace
namespace mean_field::operators {
struct PreparedStellarEquilibriumOperator::ConstructionData {
deformation::PreparedDomainDeformationRuntime domainDeformation;
field::FieldDofMap densityMap;
field::FieldDofMap displacementMap;
field::FieldDofMap gravityFluxMap;
field::FieldDofMap gravityPotentialMap;
field::FieldDofMap enthalpyMap;
field::FieldBoundaryDofMap pressureSurfaceRows;
std::array<int, StellarRootForm::value_block_count> valueSizes;
std::array<int, StellarRootForm::residual_block_count> residualSizes;
mfem::Array<int> gravityStateOffsets;
mfem::Array<int> gravityResidualOffsets;
ConstructionData(
fem::FEM &f,
deformation::PreparedDomainDeformationRuntime preparedDomainDeformation
)
: domainDeformation(std::move(preparedDomainDeformation)),
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)
),
pressureSurfaceRows(
field::make_field_boundary_dof_map<
field::Enthalpy,
utils::domain::StellarSurface,
DomainSchema>(
*f.enthalpyFes,
enthalpyMap
)
),
valueSizes(make_value_sizes(
densityMap,
domainDeformation.parameterCount(),
gravityFluxMap,
gravityPotentialMap,
enthalpyMap
)),
residualSizes(make_residual_sizes(
densityMap,
domainDeformation.parameterCount(),
gravityFluxMap,
gravityPotentialMap,
enthalpyMap
)),
gravityStateOffsets(make_gravity_state_offsets(
densityMap,
displacementMap,
gravityFluxMap,
gravityPotentialMap
)),
gravityResidualOffsets(make_gravity_residual_offsets(
gravityFluxMap,
gravityPotentialMap
)) {
}
};
PreparedStellarEquilibriumOperator::ConstructionData PreparedStellarEquilibriumOperator::MakeConstructionData(
fem::FEM &f,
deformation::PreparedDomainDeformationRuntime domainDeformation
) {
verify_coupled_discretization(f);
return ConstructionData(f, std::move(domainDeformation));
}
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
const PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
equationOfState,
std::move(fixedMassConstraint),
surfaceConstraint,
MakeConstructionData(
f,
std::move(domainDeformation)
)
) {
}
PreparedStellarEquilibriumOperator::PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
const PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
)
: mfem::Operator(
StellarEquilibriumLayout(
constructionData.valueSizes,
constructionData.residualSizes
)
.residual_offsets()
.Last(),
StellarEquilibriumLayout(
constructionData.valueSizes,
constructionData.residualSizes
)
.value_offsets()
.Last()
),
m_rootManifest(
constructionData.valueSizes,
constructionData.residualSizes,
StellarEquilibriumSpecificationModel{
equationOfState,
surface::Isobaric{
dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
fixedMassConstraint.specification()},
constructionData.pressureSurfaceRows.size()
),
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_surfaceConstraintOperator(
constructionData.pressureSurfaceRows,
surfaceConstraint
),
m_domainDeformation(std::move(constructionData.domainDeformation)),
m_fixedMassConstraint(std::move(fixedMassConstraint)) {
MFEM_VERIFY(
Width() == m_rootManifest.layout().value_offsets().Last() &&
Height() == m_rootManifest.layout().residual_offsets().Last(),
"PreparedStellarEquilibriumOperator has inconsistent block dimensions."
);
MFEM_VERIFY(
m_domainDeformation.volumeDisplacementSize() == constructionData.displacementMap.reduced_size(),
"The domain-deformation output does not match the coupled displacement discretization."
);
m_generatedDisplacementDependency.identity =
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&m_domainDeformation));
m_gravityState.SetSize(m_gravityStateOffsets.Last());
m_gravityDirection.SetSize(m_gravityStateOffsets.Last());
m_surfaceDeformationParameters.SetSize(m_domainDeformation.parameterCount());
m_generatedVolumeDisplacement.SetSize(m_domainDeformation.volumeDisplacementSize());
m_fullMechanicalResidual.SetSize(m_domainDeformation.volumeDisplacementSize());
m_volumeDisplacementDirection.SetSize(m_domainDeformation.volumeDisplacementSize());
m_fullMechanicalAction.SetSize(m_domainDeformation.volumeDisplacementSize());
m_surfaceShapeAction.SetSize(m_domainDeformation.parameterCount());
m_pullbackDerivativeAction.SetSize(m_domainDeformation.parameterCount());
m_densityVolumeIntegralAction.SetSize(1);
m_gravityState = 0.0;
m_gravityDirection = 0.0;
m_surfaceDeformationParameters = 0.0;
m_generatedVolumeDisplacement = 0.0;
m_fullMechanicalResidual = 0.0;
m_volumeDisplacementDirection = 0.0;
m_fullMechanicalAction = 0.0;
m_surfaceShapeAction = 0.0;
m_pullbackDerivativeAction = 0.0;
m_densityVolumeIntegralAction = 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.surfaceDeformation, dependencies.surfaceDeformation,
"The surface-deformation 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;
const auto rootState = m_rootManifest.stateView(state);
const auto reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationParameters =
rootState.block(utils::blocks::surface_deformation_field.parameters_term);
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
const auto gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
const auto reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
const auto bernoulli =
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
const bool generatedGeometryChanged =
!wasPrepared || dependencies.discretization != m_preparedDependencies.discretization ||
dependencies.surfaceDeformation != m_preparedDependencies.surfaceDeformation;
PreparedStellarEquilibriumReport report;
if (generatedGeometryChanged) {
m_surfaceDeformationParameters = surfaceDeformationParameters;
m_generatedGeometryReport = m_domainDeformation.buildValidatedVolumeDisplacement(
m_surfaceDeformationParameters, m_generatedVolumeDisplacement
);
++m_generatedDisplacementDependency.revision;
++m_statistics.generatedGeometryBuilds;
report.generatedVolumeDisplacement = true;
}
report.generatedGeometry = m_generatedGeometryReport;
report.generatedDisplacement = m_generatedDisplacementDependency;
pack_gravity_vector(
m_gravityState, m_gravityStateOffsets, reducedDensity, m_generatedVolumeDisplacement, gravityGradient,
gravityPotential
);
report.gravity = m_gravityOperator.Prepare(
m_gravityState, make_gravity_revisions(dependencies, m_generatedDisplacementDependency)
);
report.barotropicClosure = m_barotropicClosureOperator.Prepare(
{.density = reducedDensity, .enthalpy = reducedEnthalpy, .displacement = m_generatedVolumeDisplacement},
make_barotropic_closure_dependencies(dependencies, m_generatedDisplacementDependency)
);
report.hydrostatic = m_hydrostaticOperator.Prepare(
{.enthalpy = reducedEnthalpy,
.gravityPotential = gravityPotential,
.displacement = m_generatedVolumeDisplacement,
.bernoulliConstant = bernoulli(0)},
make_hydrostatic_dependencies(dependencies, m_generatedDisplacementDependency), rotation
);
report.displacement = m_displacementOperator.Prepare(
{.enthalpy = reducedEnthalpy},
make_displacement_dependencies(dependencies, m_generatedDisplacementDependency), rotation
);
report.massNormalization = m_massNormalizationOperator.Prepare(
m_fixedMassConstraint, make_mass_dependencies(dependencies, m_generatedDisplacementDependency)
);
report.surfaceConstraint = m_surfaceConstraintOperator.Prepare(
reducedEnthalpy, !wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy
);
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() {
mfem::Vector gravity;
mfem::Vector closure;
mfem::Vector surfaceShape;
mfem::Vector hydrostatic;
mfem::Vector mass;
m_gravityOperator.Mult(m_gravityState, gravity);
m_barotropicClosureOperator.BuildResidual(closure);
m_displacementOperator.BuildResidual(m_fullMechanicalResidual);
surfaceShape.SetSize(m_domainDeformation.parameterCount());
m_domainDeformation.applyJacobianTranspose(
m_surfaceDeformationParameters, m_fullMechanicalResidual, surfaceShape
);
m_hydrostaticOperator.BuildResidual(hydrostatic);
m_surfaceConstraintOperator.ApplyResidualRows(hydrostatic);
m_massNormalizationOperator.BuildResidual(mass);
m_cachedResidual.SetSize(Height());
m_cachedResidual = 0.0;
const auto residualView = m_rootManifest.residualView(m_cachedResidual);
MFEM_VERIFY(
gravity.Size() == residualView.block(utils::blocks::gravity_field.gradient_term).Size() +
residualView.block(utils::blocks::gravity_field.poisson_term).Size(),
"The gravity residual has the wrong size."
);
mfem::Vector gravityGradient(
gravity.GetData(), residualView.block(utils::blocks::gravity_field.gradient_term).Size()
);
mfem::Vector gravityPotential(
gravity.GetData() + gravityGradient.Size(),
residualView.block(utils::blocks::gravity_field.poisson_term).Size()
);
residualView.assign(utils::blocks::gravity_field.gradient_term, gravityGradient);
residualView.assign(utils::blocks::gravity_field.poisson_term, gravityPotential);
residualView.assign(utils::blocks::density_field.mass_term, closure);
residualView.assign(utils::blocks::surface_deformation_field.shape_equilibrium_term, surfaceShape);
residualView.assign(utils::blocks::enthalpy_field.specific_term, hydrostatic);
residualView.assign(utils::blocks::fixed_total_mass_constraint.mass_normalization_term, mass);
++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."
);
const auto rootDirection = m_rootManifest.directionView(direction);
const auto reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationDirection =
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term);
const auto gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
const auto gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
const auto reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
const auto bernoulliDirection =
rootDirection.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
m_domainDeformation.applyJacobian(
m_surfaceDeformationParameters, surfaceDeformationDirection, m_volumeDisplacementDirection
);
pack_gravity_vector(
m_gravityDirection, m_gravityStateOffsets, reducedDensityDirection, m_volumeDisplacementDirection,
gravityGradientDirection, gravityPotentialDirection
);
mfem::Vector gravityAction;
mfem::Vector closureAction;
mfem::Vector hydrostaticAction;
mfem::Vector massAction;
m_gravityJacobianOperator.Mult(m_gravityDirection, gravityAction);
m_barotropicClosureOperator.Mult(
reducedDensityDirection, reducedEnthalpyDirection, m_volumeDisplacementDirection, closureAction
);
m_displacementOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, m_volumeDisplacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
m_fullMechanicalAction
);
m_domainDeformation.applyJacobianTranspose(
m_surfaceDeformationParameters, m_fullMechanicalAction, m_surfaceShapeAction
);
m_domainDeformation.applyPullbackDerivative(
m_surfaceDeformationParameters, surfaceDeformationDirection, m_fullMechanicalResidual,
m_pullbackDerivativeAction
);
m_surfaceShapeAction += m_pullbackDerivativeAction;
m_hydrostaticOperator.ApplyCompleteJacobianAction(
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), m_volumeDisplacementDirection,
hydrostaticAction
);
m_surfaceConstraintOperator.ApplyJacobianRows(reducedEnthalpyDirection, hydrostaticAction);
m_massNormalizationOperator.ApplyCompleteJacobianAction(
reducedDensityDirection, m_volumeDisplacementDirection, massAction
);
action.SetSize(Height());
action = 0.0;
const auto actionView = m_rootManifest.residualView(action);
MFEM_VERIFY(
gravityAction.Size() == actionView.block(utils::blocks::gravity_field.gradient_term).Size() +
actionView.block(utils::blocks::gravity_field.poisson_term).Size(),
"The gravity Jacobian action has the wrong size."
);
mfem::Vector gravityGradientAction(
gravityAction.GetData(), actionView.block(utils::blocks::gravity_field.gradient_term).Size()
);
mfem::Vector gravityPotentialAction(
gravityAction.GetData() + gravityGradientAction.Size(),
actionView.block(utils::blocks::gravity_field.poisson_term).Size()
);
actionView.assign(utils::blocks::gravity_field.gradient_term, gravityGradientAction);
actionView.assign(utils::blocks::gravity_field.poisson_term, gravityPotentialAction);
actionView.assign(utils::blocks::density_field.mass_term, closureAction);
actionView.assign(utils::blocks::surface_deformation_field.shape_equilibrium_term, m_surfaceShapeAction);
actionView.assign(utils::blocks::enthalpy_field.specific_term, hydrostaticAction);
actionView.assign(utils::blocks::fixed_total_mass_constraint.mass_normalization_term, massAction);
++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() && m_surfaceConstraintOperator.IsPrepared();
}
double PreparedStellarEquilibriumOperator::GetTargetMass() const noexcept {
return m_fixedMassConstraint.targetMass().value();
}
const StellarEquilibriumLayout &PreparedStellarEquilibriumOperator::GetLayout() const noexcept {
return m_rootManifest.layout();
}
const StellarEquilibriumRootManifest &PreparedStellarEquilibriumOperator::GetRootManifest() const noexcept {
return m_rootManifest;
}
RootStateView<utils::blocks::surface_deformed_stellar_equilibrium_form>
PreparedStellarEquilibriumOperator::GetRootStateView(const mfem::Vector &state) const {
return m_rootManifest.stateView(state);
}
ResidualView<utils::blocks::surface_deformed_stellar_equilibrium_form>
PreparedStellarEquilibriumOperator::GetResidualView(mfem::Vector &residual) const {
return m_rootManifest.residualView(residual);
}
RootConstraintReport PreparedStellarEquilibriumOperator::GetFixedMassReport() const {
VerifyPrepared();
return m_rootManifest.fixedMassReport(m_massNormalizationOperator.GetCurrentMass());
}
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;
}
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralDensityAction(
const mfem::Vector &densityDirection
) const {
VerifyPrepared();
m_massNormalizationOperator.ApplyDensityJacobianAction(
densityDirection,
m_densityVolumeIntegralAction
);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,
"The density-volume integral must produce one global scalar."
);
return m_densityVolumeIntegralAction(0);
}
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralSurfaceShapeAction(
const mfem::Vector &surfaceShapeDirection
) const {
VerifyPrepared();
m_domainDeformation.applyJacobian(
m_surfaceDeformationParameters,
surfaceShapeDirection,
m_volumeDisplacementDirection
);
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
m_volumeDisplacementDirection,
m_densityVolumeIntegralAction
);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,
"The density-volume shape derivative must produce one global scalar."
);
return m_densityVolumeIntegralAction(0);
}
const PreparedPressureSurfaceConstraint &
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
return m_surfaceConstraintOperator;
}
const deformation::PreparedDomainDeformationRuntime &
PreparedStellarEquilibriumOperator::GetDomainDeformation() const noexcept {
return m_domainDeformation;
}
const mfem::Vector &PreparedStellarEquilibriumOperator::GetSurfaceDeformationParameters() const {
VerifyPrepared();
return m_surfaceDeformationParameters;
}
const mfem::Vector &PreparedStellarEquilibriumOperator::GetGeneratedVolumeDisplacement() const {
VerifyPrepared();
return m_generatedVolumeDisplacement;
}
const mfem::Vector &PreparedStellarEquilibriumOperator::GetFullMechanicalResidual() const {
VerifyPrepared();
return m_fullMechanicalResidual;
}
const StellarEquilibriumDependencyStamp &
PreparedStellarEquilibriumOperator::GetGeneratedDisplacementDependency() const {
VerifyPrepared();
return m_generatedDisplacementDependency;
}
void PreparedStellarEquilibriumOperator::VerifyPrepared() const {
MFEM_VERIFY(
IsPrepared(), "PreparedStellarEquilibriumOperator must be prepared before residual or Jacobian application."
);
}
} // namespace mean_field::operators

View File

@@ -1,246 +1,58 @@
module;
#include "mfem.hpp"
#include "profile.h"
#include <array>
#include <cmath>
#include <format>
#include <source_location>
#include <string_view>
#include <unordered_map>
module mean_field;
import :mapping.coefficients;
import :analysis.integral;
namespace {
double centrifugal_potential(
const mfem::Vector &phys_x,
const double omega
) {
const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2);
return -0.5 * s2 * std::pow(omega, 2);
}
void grid_function_to_true_dofs(
const mfem::ParFiniteElementSpace &finite_element_space,
const mfem::GridFunction &grid_function,
mfem::Vector &true_dofs
) {
MFEM_VERIFY(
grid_function.Size() == finite_element_space.GetVSize(),
"The grid function does not match the requested finite-element "
"space."
);
true_dofs.SetSize(finite_element_space.GetTrueVSize());
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."
);
true_dofs = grid_function;
}
}
} // namespace
namespace mean_field::physics {
GravitySolution grav_potential(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool phi_warm
) {
MFEM_VERIFY(
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::Array<int> outer_bdr_marker(f.mesh->bdr_attributes.Max());
outer_bdr_marker = 0;
outer_bdr_marker[1] = 1;
mfem::ParLinearForm g_rhs(f.gravityFluxFes.get());
// ReSharper disable once CppTooWideScope
std::unique_ptr<mfem::Coefficient> boundary_potential_coeff;
if (!f.has_mapping()) { // We only need to explicitly add a boundary
// integrator if a mapping is not being used. In
// the case where the outer domain has been
// compactified the φ=0 boundary condition is
// the natural condition and MFEM automatically
// handles this
auto boundary_potential = [&f](const mfem::Vector &x_physical) {
return l2_multipole_potential(f, utils::MASS, x_physical);
};
boundary_potential_coeff =
std::make_unique<mfem::FunctionCoefficient>(boundary_potential);
auto boundary_integrator =
std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(
*boundary_potential_coeff
);
const mfem::FiniteElement &boundary_element =
*f.gravityFluxFes->GetTypicalTraceElement();
f.quadratureFactory->configure_gravity_boundary(
*boundary_integrator,
quadrature::QuadratureRole::discretization, boundary_element,
utils::DOMAINS::VACUUM, quadrature::MappingKind::none
);
g_rhs.AddBoundaryIntegrator(
boundary_integrator.release(), outer_bdr_marker
);
}
g_rhs.Assemble();
mfem::GridFunctionCoefficient rho_coeff(&rho);
mfem::ConstantCoefficient G4pi(4.0 * M_PI * utils::G);
mfem::ProductCoefficient source_coeff(G4pi, rho_coeff);
mfem::ParLinearForm f_rhs(f.gravityPotentialFes.get());
std::unique_ptr<mfem::Coefficient> mapped_source_coeff;
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<mapping::MappedScalarCoefficient>(
*f.mapping, source_coeff
);
active_source_coeff = mapped_source_coeff.get();
source_mapping_kind = quadrature::MappingKind::general;
}
auto source_integrator =
std::make_unique<mfem::DomainLFIntegrator>(*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
);
f_rhs.Assemble();
mfem::BlockVector RHS(f.gravityBlockTrueOffsets);
RHS.GetBlock(0) = *g_rhs.ParallelAssemble();
RHS.GetBlock(1) = *f_rhs.ParallelAssemble();
mfem::BlockVector X(f.gravityBlockTrueOffsets);
X = 0.0;
f.gravityContext.minres->SetOperator(*f.gravityContext.block_A);
f.gravityContext.minres->Mult(RHS, X);
GravitySolution solution(f);
solution.gradPhi.SetFromTrueDofs(X.GetBlock(0));
solution.phi.SetFromTrueDofs(X.GetBlock(1));
return solution;
}
mfem::GridFunction get_potential(
fem::FEM &fem,
const utils::Args &args,
const mfem::GridFunction &rho,
const bool warm
) {
auto phi = grav_potential(fem, args, rho, warm);
if (args.r.enabled) {
auto rot = [&fem, &args](const mfem::Vector &x) {
mfem::Vector rel_x = x;
rel_x -= fem.com;
return centrifugal_potential(rel_x, args.r.omega);
};
std::unique_ptr<mfem::Coefficient> centrifugal_coeff;
if (fem.has_mapping()) {
centrifugal_coeff = std::make_unique<
mapping::PhysicalPositionFunctionCoefficient>(
*fem.mapping, rot
);
} else {
centrifugal_coeff =
std::make_unique<mfem::FunctionCoefficient>(rot);
}
mfem::GridFunction centrifugal_gf(fem.gravityPotentialFes.get());
centrifugal_gf.ProjectCoefficient(*centrifugal_coeff);
phi.phi += centrifugal_gf;
}
return phi.phi;
}
mfem::DenseMatrix compute_quadrupole_moment_tensor(
const fem::FEM &fem,
const mfem::GridFunction &rho,
const mfem::Vector &com
) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::quadrupole", 0);
const int dim = fem.mesh->Dimension();
mfem::DenseMatrix local_Q(dim, dim);
local_Q = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
std::uint64_t mapping_evaluations = 0;
mapping::VolumeMappingContext mapping_context;
mfem::Vector x_prime(dim);
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
if (fem.mesh->GetAttribute(i) == 3)
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *trans =
fem.mesh->GetElementTransformation(i);
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
using DensityField = field::Field<field::Density>;
const quadrature::Query query =
DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, trans->OrderW(),
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
fem.has_mapping() ? quadrature::MappingKind::general
: quadrature::MappingKind::none
const quadrature::Query query = DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, trans->OrderW(), std::array<int, 1>{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);
trans->SetIntPoint(&ip);
double weight = trans->Weight() * ip.weight;
if (fem.has_mapping()) {
weight *= fem.mapping->ComputeDetJ(*trans, ip);
}
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
"Quadrupole integration encountered an invalid mapping."
);
++mapping_evaluations;
const double weight = mapping_context.quadrature.weight;
const double rho_val = rho.GetValue(i, ip);
mfem::Vector phys_point(dim);
if (fem.has_mapping()) {
fem.mapping->GetPhysicalPoint(*trans, ip, phys_point);
} else {
trans->Transform(ip, phys_point);
}
const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
mfem::Vector x_prime(dim);
double r_sq = 0.0;
for (int d = 0; d < dim; ++d) {
@@ -251,19 +63,17 @@ 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 contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
local_Q(m, n) += rho_val * contrib * weight;
}
}
}
}
MEAN_FIELD_PROFILE_COUNT("analysis::quadrupole mapping evaluations", mapping_evaluations);
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 +99,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;
@@ -298,236 +107,32 @@ namespace mean_field::physics {
return l0_contrib + l2_contrib;
}
void update_stiffness_matrix(fem::FEM &f) {
mfem::Array<int> empty_tdofs;
// ==========================================
// 1. Partially Assemble the High-Order Mass Block
// ==========================================
f.gravityContext.m_form =
std::make_unique<mfem::ParBilinearForm>(f.gravityFluxFes.get());
f.gravityContext.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
std::unique_ptr<mfem::VectorFEMassIntegrator> hdiv_mass_integrator;
if (f.has_mapping()) {
f.gravityContext.mapped_hdiv_mass_coeff =
std::make_unique<mapping::MappedHDivMassCoefficient>(
*f.mapping, f.mesh->Dimension()
);
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>(
*f.gravityContext.mapped_hdiv_mass_coeff
);
} else {
f.gravityContext.mapped_hdiv_mass_coeff.reset();
hdiv_mass_integrator =
std::make_unique<mfem::VectorFEMassIntegrator>();
}
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.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()
);
f.gravityContext.m_form->Assemble();
// ==========================================
// 2. Partially Assemble the High-Order Divergence Block
// ==========================================
f.gravityContext.b_form = std::make_unique<mfem::ParMixedBilinearForm>(
f.gravityFluxFes.get(), f.gravityPotentialFes.get()
);
f.gravityContext.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
auto divergence_discretization_integrator =
std::make_unique<mfem::VectorFEDivergenceIntegrator>();
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()
);
f.gravityContext.b_form->Assemble();
MFEM_VERIFY(
f.domainMapperStateless != nullptr,
"Gravity source partial assembly requires the stateless domain "
"mapper."
);
mfem::Vector displacement_true(f.displacementFes->GetTrueVSize());
displacement_true = 0.0;
const mfem::GridFunction *active_displacement =
f.mapping->GetDisplacement();
if (active_displacement != nullptr) {
grid_function_to_true_dofs(
*f.displacementFes, *active_displacement, displacement_true
);
}
auto source_form =
std::make_unique<operators::PreparedMappedGravitySourceOperator>(
f, *f.domainMapperStateless
);
source_form->Prepare(displacement_true);
f.gravityContext.source_form = std::move(source_form);
// ==========================================
// 3. Assemble Global Block Operator
// ==========================================
f.gravityContext.BT = std::make_unique<mfem::TransposeOperator>(
f.gravityContext.b_form.get()
);
f.gravityContext.block_A =
std::make_unique<mfem::BlockOperator>(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());
// ==========================================
// 4. Construct a mapped Schur preconditioner
// ==========================================
mfem::Vector mass_diagonal(f.gravityFluxFes->GetTrueVSize());
f.gravityContext.m_form->AssembleDiagonal(mass_diagonal);
mfem::Vector inverse_mass_diagonal(mass_diagonal);
for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) {
MFEM_VERIFY(
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."
);
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::VectorFEDivergenceIntegrator>();
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()
);
b_preconditioner.Assemble();
b_preconditioner.Finalize();
std::unique_ptr<mfem::HypreParMatrix> b_matrix(
b_preconditioner.ParallelAssemble()
);
std::unique_ptr<mfem::HypreParMatrix> 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())
);
// ==========================================
// 5. Wire Up the preconditioners
// ==========================================
f.gravityContext.prec_M =
std::make_unique<mfem::OperatorJacobiSmoother>(
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()
);
}
GravitySolution grav_potential_new(
GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,
const GravitySolveOptions &options,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
MEAN_FIELD_PROFILE_SCOPE_WARMUP("physics::solve_gravity_field", 0);
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."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"Gravity initialization requires the density finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"Gravity initialization requires the gravity-potential "
f.gravityPotentialFes != nullptr, "Gravity initialization requires the gravity-potential "
"finite-element "
"space."
);
MFEM_VERIFY(
f.gravityFluxFes != nullptr,
"Gravity initialization requires the "
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.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.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 "
rho.FESpace() == f.densityFes.get(), "Gravity initialization requires density to use the FEM density "
"space."
);
MFEM_VERIFY(
@@ -536,99 +141,153 @@ namespace mean_field::physics {
"Vec_H1 "
"space."
);
MFEM_VERIFY(
std::isfinite(options.relativeTolerance) && options.relativeTolerance >= 0.0,
"Gravity solve requires a finite, nonnegative relative tolerance."
);
MFEM_VERIFY(
std::isfinite(options.absoluteTolerance) && options.absoluteTolerance >= 0.0,
"Gravity solve requires a finite, nonnegative absolute tolerance."
);
MFEM_VERIFY(options.maximumIterations > 0, "Gravity solve requires a positive MINRES iteration limit.");
using form = utils::blocks::gravity_field_form;
constexpr auto gravity_gradient_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.gradient_term
);
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual_block =
utils::blocks::get_residual_block<form>(
utils::blocks::gravity_field.poisson_term
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term);
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
const field::FieldDofGridFunctionAdapter density_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: density map", 0,
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes)
);
const field::FieldDofGridFunctionAdapter displacement_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: displacement map", 0,
field::make_field_dof_grid_function_adapter<field::Displacement, DomainSchema>(*f.displacementFes)
);
const field::FieldDofGridFunctionAdapter gravity_flux_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: flux map", 0,
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityFluxFes)
);
const field::FieldDofGridFunctionAdapter gravity_potential_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: potential map", 0,
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityPotentialFes)
);
const field::FieldDofMap &density_map = density_adapter.dof_map();
const field::FieldDofMap &displacement_map = displacement_adapter.dof_map();
const field::FieldDofMap &gravity_flux_map = gravity_flux_adapter.dof_map();
const field::FieldDofMap &gravity_potential_map = gravity_potential_adapter.dof_map();
const std::array<int, form::value_block_count> value_sizes{
f.densityFes->GetTrueVSize(), f.displacementFes->GetTrueVSize(),
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
density_map.reduced_size(), displacement_map.reduced_size(), gravity_flux_map.reduced_size(),
gravity_potential_map.reduced_size()
};
const std::array<int, form::residual_block_count> residual_sizes{
f.gravityFluxFes->GetTrueVSize(),
f.gravityPotentialFes->GetTrueVSize()
gravity_flux_map.reduced_size(), gravity_potential_map.reduced_size()
};
const utils::blocks::form_layout<form> layout(
value_sizes, residual_sizes
const utils::blocks::form_layout<form> layout = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: block layout", 0, utils::blocks::form_layout<form>(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
const mfem::Vector density =
MEAN_FIELD_PROFILE_EVALUATE_WARMUP("gravity solve: gather density", 0, density_adapter.gather(rho));
const mfem::Vector reduced_displacement = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: gather displacement", 0, displacement_adapter.gather(displacement)
);
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()
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context =
MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: linearization context", 0,
operators::context::gravity_field::GravityFieldLinearizationContext(f, *f.domainMapperStateless)
);
operators::GravityFieldOperator gravity_operator(
f, *f.domainMapperStateless, linearization_context,
layout.value_offsets(), gravity_jacobian
operators::GravityFieldJacobianOperator gravity_jacobian = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: jacobian operator", 0,
operators::GravityFieldJacobianOperator(
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
)
);
operators::context::gravity_field::GravityFieldGeometryContext
reduced_geometry_context(f, *f.domainMapperStateless);
operators::GravityFieldOperator gravity_operator = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: nonlinear operator", 0,
operators::GravityFieldOperator(
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
)
);
operators::ReducedGravityFieldOperator reduced_operator(
gravity_operator, reduced_geometry_context, displacement_true
operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context =
MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: reduced geometry context", 0,
operators::context::gravity_field::GravityFieldGeometryContext(f, *f.domainMapperStateless)
);
operators::ReducedGravityFieldOperator reduced_operator = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: reduced operator", 0,
operators::ReducedGravityFieldOperator(gravity_operator, reduced_geometry_context, reduced_displacement)
);
operators::ReducedGravityFieldPreconditioner reduced_preconditioner = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
"gravity solve: preconditioner construction", 0,
operators::ReducedGravityFieldPreconditioner(f, reduced_geometry_context)
);
mfem::Vector right_hand_side;
reduced_operator.BuildRightHandSide(density_true, right_hand_side);
MEAN_FIELD_PROFILE_CALL_WARMUP(
"gravity solve: right-hand side", 0, reduced_operator.BuildRightHandSide(density, right_hand_side)
);
MFEM_VERIFY(
right_hand_side.Size() == reduced_operator.Height(),
"The reduced gravity right-hand side has the wrong size."
);
mfem::BlockVector gravity_state(
reduced_operator.GetGravityTrueOffsets()
);
mfem::BlockVector gravity_state(reduced_operator.GetGravityOffsets());
gravity_state = 0.0;
mfem::MINRESSolver minres(f.mesh->GetComm());
minres.SetOperator(reduced_operator);
minres.SetPreconditioner(*f.gravityContext.block_prec);
minres.SetRelTol(args.p.rtol);
minres.SetAbsTol(args.p.atol);
minres.SetMaxIter(args.p.max_iters);
minres.SetPrintLevel(1);
minres.Mult(right_hand_side, gravity_state);
minres.SetPreconditioner(reduced_preconditioner);
minres.SetRelTol(options.relativeTolerance);
minres.SetAbsTol(options.absoluteTolerance);
minres.SetMaxIter(options.maximumIterations);
// minres.SetPrintLevel(args.verbose ? 1 : 0);
minres.SetPrintLevel(0);
MEAN_FIELD_PROFILE_CALL_WARMUP("gravity solve: MINRES", 0, minres.Mult(right_hand_side, gravity_state));
MEAN_FIELD_PROFILE_COUNT("gravity solve: MINRES iterations", minres.GetNumIterations());
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.phi.SetFromTrueDofs(
gravity_state.GetBlock(gravity_poisson_residual_block)
MEAN_FIELD_PROFILE_CALL_WARMUP(
"gravity solve: scatter solution", 0,
gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi)
);
return solution;
}
GravitySolution solve_gravity_field(
fem::FEM &f,
const utils::Args &args,
const mfem::GridFunction &rho,
const mfem::GridFunction &displacement
) {
return solve_gravity_field(
f,
GravitySolveOptions{
.relativeTolerance = args.p.rtol,
.absoluteTolerance = args.p.atol,
.maximumIterations = args.p.max_iters
},
rho, displacement
);
}
} // namespace mean_field::physics

View File

@@ -10,23 +10,22 @@ namespace mean_field::physics {
const mfem::GridFunction &rho_ref
) {
double local_I = 0.0;
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
for (int i = 0; i < fem.mesh->GetNE(); i++) {
if (fem.mesh->GetAttribute(i) == 3)
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
continue;
mfem::ElementTransformation *T =
fem.mesh->GetElementTransformation(i);
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(i);
using DensityField = field::Field<field::Density>;
const quadrature::Query query =
DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, T->OrderW(),
std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
const quadrature::Query query = DensityField::make_query<field::Density::Form::Quadrupole>(
quadrature::QuadratureRole::diagnostic, T->OrderW(), std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
quadrature::MappingKind::general
);
const mfem::IntegrationRule &ir =
*fem.quadratureFactory->get(query, T->GetGeometryType())
.integration_rule;
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);
@@ -34,22 +33,22 @@ namespace mean_field::physics {
const double rho_hat = rho_ref.GetValue(i, ip);
mfem::Vector x_phys;
fem.mapping->GetPhysicalPoint(*T, ip, x_phys);
mapping::VolumeMappingContext mapping_context;
MFEM_VERIFY(
mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) == mapping::MappingStatus::valid,
"Moment-of-inertia integration encountered an invalid mapping."
);
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
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 weight = mapping_context.quadrature.weight;
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;
}

View File

@@ -0,0 +1,77 @@
module;
#include <cmath>
#include <memory>
#include <mfem.hpp>
#include <stdexcept>
module mean_field;
import :preconditioning.gravity_field;
namespace mean_field::preconditioning {
std::unique_ptr<mfem::HypreParMatrix> assembleGravityDivergenceSurrogate(const fem::FEM &f) {
if (f.mesh == nullptr || f.gravityFluxFes == nullptr || f.gravityPotentialFes == nullptr ||
f.quadratureFactory == nullptr) {
throw std::invalid_argument(
"The gravity divergence surrogate requires its mesh, gravity spaces, and quadrature policy."
);
}
mfem::ParMixedBilinearForm divergence(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
auto integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
const mfem::FiniteElement &trialElement = *f.gravityFluxFes->GetTypicalFE();
const mfem::FiniteElement &testElement = *f.gravityPotentialFes->GetTypicalFE();
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(0);
f.quadratureFactory->configure_gravity_divergence(
*integrator, quadrature::QuadratureRole::preconditioner, trialElement, testElement, transformation,
utils::DOMAINS::ALL, quadrature::MappingKind::none
);
divergence.AddDomainIntegrator(integrator.release());
divergence.Assemble();
divergence.Finalize();
std::unique_ptr<mfem::HypreParMatrix> assembled(divergence.ParallelAssemble());
if (assembled == nullptr) {
throw std::runtime_error("MFEM did not assemble the gravity divergence surrogate.");
}
return assembled;
}
std::unique_ptr<mfem::HypreParMatrix> assembleGravityPotentialSchurSurrogate(
const fem::FEM &f,
const mfem::Vector &trueMassDiagonal
) {
if (f.gravityFluxFes == nullptr || trueMassDiagonal.Size() != f.gravityFluxFes->GetTrueVSize()) {
throw std::invalid_argument(
"The gravity Schur surrogate requires one mass-diagonal entry per true gravity-gradient DOF."
);
}
mfem::Vector inverseMassDiagonal(trueMassDiagonal);
for (int index = 0; index < inverseMassDiagonal.Size(); ++index) {
const double entry = inverseMassDiagonal(index);
if (!std::isfinite(entry) || entry <= 0.0) {
throw std::invalid_argument(
"The gravity Schur surrogate encountered a non-positive or non-finite mass diagonal."
);
}
inverseMassDiagonal(index) = 1.0 / entry;
}
std::unique_ptr<mfem::HypreParMatrix> divergence = assembleGravityDivergenceSurrogate(f);
std::unique_ptr<mfem::HypreParMatrix> inverseMassDivergenceTranspose(divergence->Transpose());
inverseMassDivergenceTranspose->ScaleRows(inverseMassDiagonal);
std::unique_ptr<mfem::HypreParMatrix> schur(
mfem::ParMult(divergence.get(), inverseMassDivergenceTranspose.get())
);
if (schur == nullptr) {
throw std::runtime_error("MFEM did not assemble the gravity potential-Schur surrogate.");
}
return schur;
}
} // namespace mean_field::preconditioning

View File

@@ -0,0 +1,530 @@
#include "profile.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <utility>
namespace {
struct MpiContext {
bool active{false};
int rank{0};
int size{1};
};
void check_mpi(
const int result,
const std::string_view operation
) {
if (result == MPI_SUCCESS) {
return;
}
std::array<char, MPI_MAX_ERROR_STRING> buffer{};
int length = 0;
MPI_Error_string(result, buffer.data(), &length);
throw std::runtime_error(
"MPI profiling operation '" + std::string(operation) +
"' failed: " + std::string(buffer.data(), static_cast<std::size_t>(length))
);
}
[[nodiscard]] MpiContext get_mpi_context(const MPI_Comm communicator) {
int initialized = 0;
check_mpi(MPI_Initialized(&initialized), "MPI_Initialized");
if (initialized == 0) {
return {};
}
int finalized = 0;
check_mpi(MPI_Finalized(&finalized), "MPI_Finalized");
if (finalized != 0) {
return {};
}
if (communicator == MPI_COMM_NULL) {
throw std::invalid_argument("Profiling aggregation requires a valid MPI communicator.");
}
MpiContext context{.active = true};
check_mpi(MPI_Comm_rank(communicator, &context.rank), "MPI_Comm_rank");
check_mpi(MPI_Comm_size(communicator, &context.size), "MPI_Comm_size");
return context;
}
[[nodiscard]] std::string count_range(
const std::uint64_t minimum,
const std::uint64_t maximum
) {
if (minimum == maximum) {
return std::to_string(minimum);
}
return std::to_string(minimum) + "-" + std::to_string(maximum);
}
void write_csv_field(
std::ostream &stream,
const std::string_view field
) {
stream << '"';
for (const char character : field) {
if (character == '"') {
stream << "\"\"";
} else {
stream << character;
}
}
stream << '"';
}
} // namespace
namespace mean_field::profiling {
struct Registry::Impl {
struct Entry {
std::string label;
Statistics statistics;
};
mutable std::mutex mutex;
std::map<std::string, std::size_t, std::less<>> indices;
std::vector<Entry> entries;
};
Registry &Registry::Get() {
static Registry registry;
return registry;
}
Registry::Registry() : m_impl(std::make_unique<Impl>()) {
}
Registry::~Registry() = default;
std::size_t Registry::Register(
const std::string_view label,
const std::uint64_t warmup_count
) {
if (label.empty()) {
throw std::invalid_argument("A profiling region label cannot be empty.");
}
if (label.find('\0') != std::string_view::npos) {
throw std::invalid_argument("A profiling region label cannot contain a null byte.");
}
std::scoped_lock lock(m_impl->mutex);
if (const auto iterator = m_impl->indices.find(label); iterator != m_impl->indices.end()) {
Impl::Entry &entry = m_impl->entries[iterator->second];
entry.statistics.warmup_target = std::max(entry.statistics.warmup_target, warmup_count);
return iterator->second;
}
const std::size_t index = m_impl->entries.size();
Impl::Entry entry{.label = std::string(label)};
entry.statistics.warmup_target = warmup_count;
m_impl->entries.push_back(std::move(entry));
m_impl->indices.emplace(m_impl->entries.back().label, index);
return index;
}
void Registry::Record(
const std::string_view label,
const double seconds,
const std::uint64_t warmup_count
) {
if (!std::isfinite(seconds) || seconds < 0.0) {
throw std::invalid_argument("A profiling duration must be finite and nonnegative.");
}
const std::size_t region = Register(label, warmup_count);
std::scoped_lock lock(m_impl->mutex);
Statistics &statistics = m_impl->entries[region].statistics;
const bool is_warmup = statistics.observations < statistics.warmup_target;
++statistics.observations;
if (is_warmup) {
++statistics.warmups;
return;
}
++statistics.samples;
statistics.total_seconds += seconds;
if (statistics.samples == 1) {
statistics.minimum_seconds = seconds;
statistics.maximum_seconds = seconds;
} else {
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
}
}
void Registry::AddCount(
const std::string_view label,
const std::uint64_t work_units
) {
const std::size_t region = Register(label, 0);
std::scoped_lock lock(m_impl->mutex);
Statistics &statistics = m_impl->entries[region].statistics;
if (work_units > std::numeric_limits<std::uint64_t>::max() - statistics.work_units) {
throw std::overflow_error("A profiling work counter overflowed.");
}
statistics.work_units += work_units;
}
void Registry::Record(
const std::size_t region,
const double seconds
) noexcept {
if (!std::isfinite(seconds) || seconds < 0.0) {
return;
}
try {
std::scoped_lock lock(m_impl->mutex);
if (region >= m_impl->entries.size()) {
return;
}
Statistics &statistics = m_impl->entries[region].statistics;
const bool is_warmup = statistics.observations < statistics.warmup_target;
++statistics.observations;
if (is_warmup) {
++statistics.warmups;
return;
}
++statistics.samples;
statistics.total_seconds += seconds;
if (statistics.samples == 1) {
statistics.minimum_seconds = seconds;
statistics.maximum_seconds = seconds;
} else {
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
}
} catch (...) {
}
}
void Registry::AddCount(
const std::size_t region,
const std::uint64_t work_units
) noexcept {
try {
std::scoped_lock lock(m_impl->mutex);
if (region >= m_impl->entries.size()) {
return;
}
Statistics &statistics = m_impl->entries[region].statistics;
if (work_units > std::numeric_limits<std::uint64_t>::max() - statistics.work_units) {
statistics.work_units = std::numeric_limits<std::uint64_t>::max();
} else {
statistics.work_units += work_units;
}
} catch (...) {
}
}
void Registry::Reset() {
std::scoped_lock lock(m_impl->mutex);
for (Impl::Entry &entry : m_impl->entries) {
const std::uint64_t warmup_target = entry.statistics.warmup_target;
entry.statistics = {};
entry.statistics.warmup_target = warmup_target;
}
}
std::map<
std::string,
Statistics,
std::less<>>
Registry::Snapshot() const {
std::map<std::string, Statistics, std::less<>> snapshot;
std::scoped_lock lock(m_impl->mutex);
for (const Impl::Entry &entry : m_impl->entries) {
snapshot.emplace(entry.label, entry.statistics);
}
return snapshot;
}
std::vector<DistributedStatistics> Registry::Aggregate(const MPI_Comm communicator) const {
const std::map<std::string, Statistics, std::less<>> local_snapshot = Snapshot();
const MpiContext mpi_context = get_mpi_context(communicator);
std::vector<std::string> labels;
if (!mpi_context.active) {
labels.reserve(local_snapshot.size());
for (const auto &[label, statistics] : local_snapshot) {
(void)statistics;
labels.push_back(label);
}
} else {
std::string serialized_labels;
for (const auto &[label, statistics] : local_snapshot) {
(void)statistics;
serialized_labels.append(label);
serialized_labels.push_back('\0');
}
if (serialized_labels.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
throw std::overflow_error("The local profiling label table is too large for MPI_Allgatherv.");
}
const int local_bytes = static_cast<int>(serialized_labels.size());
std::vector<int> byte_counts(static_cast<std::size_t>(mpi_context.size));
check_mpi(
MPI_Allgather(&local_bytes, 1, MPI_INT, byte_counts.data(), 1, MPI_INT, communicator),
"MPI_Allgather(profile label sizes)"
);
std::vector<int> displacements(static_cast<std::size_t>(mpi_context.size));
int total_bytes = 0;
for (int rank = 0; rank < mpi_context.size; ++rank) {
if (byte_counts[rank] < 0 || byte_counts[rank] > std::numeric_limits<int>::max() - total_bytes) {
throw std::overflow_error("The distributed profiling label table is too large for MPI_Allgatherv.");
}
displacements[rank] = total_bytes;
total_bytes += byte_counts[rank];
}
std::vector<char> all_serialized_labels(static_cast<std::size_t>(total_bytes));
check_mpi(
MPI_Allgatherv(
serialized_labels.data(), local_bytes, MPI_CHAR, all_serialized_labels.data(), byte_counts.data(),
displacements.data(), MPI_CHAR, communicator
),
"MPI_Allgatherv(profile labels)"
);
std::set<std::string, std::less<>> unique_labels;
for (int rank = 0; rank < mpi_context.size; ++rank) {
const char *position = all_serialized_labels.data() + displacements[rank];
const char *end = position + byte_counts[rank];
while (position != end) {
const void *terminator_address =
std::memchr(position, '\0', static_cast<std::size_t>(end - position));
if (terminator_address == nullptr) {
throw std::runtime_error("A distributed profiling label table is malformed.");
}
const auto *terminator = static_cast<const char *>(terminator_address);
unique_labels.emplace(position, terminator);
position = terminator + 1;
}
}
labels.assign(unique_labels.begin(), unique_labels.end());
}
std::vector<DistributedStatistics> aggregate(labels.size());
if (labels.empty()) {
return aggregate;
}
std::vector<std::uint64_t> local_samples(labels.size(), 0);
std::vector<std::uint64_t> local_warmups(labels.size(), 0);
std::vector<std::uint64_t> local_work_units(labels.size(), 0);
std::vector<double> local_averages(labels.size(), 0.0);
std::vector<double> local_minima(labels.size(), std::numeric_limits<double>::infinity());
std::vector<double> local_maxima(labels.size(), 0.0);
std::vector<double> local_totals(labels.size(), 0.0);
for (std::size_t index = 0; index < labels.size(); ++index) {
if (const auto iterator = local_snapshot.find(labels[index]); iterator != local_snapshot.end()) {
const Statistics &statistics = iterator->second;
local_samples[index] = statistics.samples;
local_warmups[index] = statistics.warmups;
local_work_units[index] = statistics.work_units;
local_totals[index] = statistics.total_seconds;
if (statistics.samples != 0) {
local_averages[index] = statistics.total_seconds / static_cast<double>(statistics.samples);
local_minima[index] = statistics.minimum_seconds;
local_maxima[index] = statistics.maximum_seconds;
}
}
}
std::vector<std::uint64_t> minimum_samples = local_samples;
std::vector<std::uint64_t> maximum_samples = local_samples;
std::vector<std::uint64_t> maximum_warmups = local_warmups;
std::vector<std::uint64_t> minimum_work_units = local_work_units;
std::vector<std::uint64_t> maximum_work_units = local_work_units;
std::vector<double> maximum_rank_averages = local_averages;
std::vector<double> global_minima = local_minima;
std::vector<double> global_maxima = local_maxima;
std::vector<double> maximum_rank_totals = local_totals;
if (mpi_context.active) {
if (labels.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
throw std::overflow_error("There are too many profiling regions for one MPI reduction.");
}
const int count = static_cast<int>(labels.size());
check_mpi(
MPI_Allreduce(local_samples.data(), minimum_samples.data(), count, MPI_UINT64_T, MPI_MIN, communicator),
"MPI_Allreduce(minimum profile samples)"
);
check_mpi(
MPI_Allreduce(local_samples.data(), maximum_samples.data(), count, MPI_UINT64_T, MPI_MAX, communicator),
"MPI_Allreduce(maximum profile samples)"
);
check_mpi(
MPI_Allreduce(local_warmups.data(), maximum_warmups.data(), count, MPI_UINT64_T, MPI_MAX, communicator),
"MPI_Allreduce(profile warmups)"
);
check_mpi(
MPI_Allreduce(
local_work_units.data(), minimum_work_units.data(), count, MPI_UINT64_T, MPI_MIN, communicator
),
"MPI_Allreduce(minimum profile work)"
);
check_mpi(
MPI_Allreduce(
local_work_units.data(), maximum_work_units.data(), count, MPI_UINT64_T, MPI_MAX, communicator
),
"MPI_Allreduce(maximum profile work)"
);
check_mpi(
MPI_Allreduce(
local_averages.data(), maximum_rank_averages.data(), count, MPI_DOUBLE, MPI_MAX, communicator
),
"MPI_Allreduce(profile averages)"
);
check_mpi(
MPI_Allreduce(local_minima.data(), global_minima.data(), count, MPI_DOUBLE, MPI_MIN, communicator),
"MPI_Allreduce(profile minima)"
);
check_mpi(
MPI_Allreduce(local_maxima.data(), global_maxima.data(), count, MPI_DOUBLE, MPI_MAX, communicator),
"MPI_Allreduce(profile maxima)"
);
check_mpi(
MPI_Allreduce(
local_totals.data(), maximum_rank_totals.data(), count, MPI_DOUBLE, MPI_MAX, communicator
),
"MPI_Allreduce(profile totals)"
);
}
for (std::size_t index = 0; index < labels.size(); ++index) {
aggregate[index] = {
.label = labels[index],
.minimum_samples = minimum_samples[index],
.maximum_samples = maximum_samples[index],
.maximum_warmups = maximum_warmups[index],
.minimum_work_units = minimum_work_units[index],
.maximum_work_units = maximum_work_units[index],
.maximum_rank_average_seconds = maximum_rank_averages[index],
.global_minimum_seconds = std::isfinite(global_minima[index]) ? global_minima[index] : 0.0,
.global_maximum_seconds = global_maxima[index],
.maximum_rank_total_seconds = maximum_rank_totals[index]
};
}
return aggregate;
}
void Registry::Print(
const MPI_Comm communicator,
std::ostream &stream
) const {
const std::vector<DistributedStatistics> aggregate = Aggregate(communicator);
const MpiContext mpi_context = get_mpi_context(communicator);
if (mpi_context.rank != 0) {
return;
}
std::ios old_state(nullptr);
old_state.copyfmt(stream);
stream << '\n';
stream << std::left << std::setw(58) << "Profile Region" << std::right << std::setw(13) << "Samples"
<< std::setw(11) << "Warmups" << std::setw(15) << "Work/rank" << std::setw(14) << "Avg max ms"
<< std::setw(14) << "Min ms" << std::setw(14) << "Max ms" << std::setw(14) << "Total max s" << '\n';
stream << std::string(153, '-') << '\n';
for (const DistributedStatistics &statistics : aggregate) {
stream << std::left << std::setw(58) << statistics.label << std::right << std::setw(13)
<< count_range(statistics.minimum_samples, statistics.maximum_samples) << std::setw(11)
<< statistics.maximum_warmups << std::setw(15)
<< count_range(statistics.minimum_work_units, statistics.maximum_work_units) << std::setw(14)
<< std::fixed << std::setprecision(3) << 1.0e3 * statistics.maximum_rank_average_seconds
<< std::setw(14) << 1.0e3 * statistics.global_minimum_seconds << std::setw(14)
<< 1.0e3 * statistics.global_maximum_seconds << std::setw(14) << std::setprecision(6)
<< statistics.maximum_rank_total_seconds << '\n';
}
stream << std::string(153, '=') << '\n';
stream << "MPI ranks: " << mpi_context.size << "\n\n";
stream.copyfmt(old_state);
}
void Registry::Print(const MPI_Comm communicator) const {
Print(communicator, std::cout);
}
void Registry::PrintCsv(
const MPI_Comm communicator,
std::ostream &stream
) const {
const std::vector<DistributedStatistics> aggregate = Aggregate(communicator);
const MpiContext mpi_context = get_mpi_context(communicator);
if (mpi_context.rank != 0) {
return;
}
stream << "label,minimum_samples,maximum_samples,maximum_warmups,minimum_work_units,maximum_work_units,"
"maximum_rank_average_seconds,global_minimum_seconds,global_maximum_seconds,"
"maximum_rank_total_seconds,mpi_ranks\n";
for (const DistributedStatistics &statistics : aggregate) {
write_csv_field(stream, statistics.label);
stream << ',' << statistics.minimum_samples << ',' << statistics.maximum_samples << ','
<< statistics.maximum_warmups << ',' << statistics.minimum_work_units << ','
<< statistics.maximum_work_units << ',' << std::setprecision(17)
<< statistics.maximum_rank_average_seconds << ',' << statistics.global_minimum_seconds << ','
<< statistics.global_maximum_seconds << ',' << statistics.maximum_rank_total_seconds << ','
<< mpi_context.size << '\n';
}
}
Region::Region(
const std::string_view label,
const std::uint64_t warmup_count
)
: m_region(
Registry::Get().Register(
label,
warmup_count
)
) {
}
void Region::Record(const double seconds) const noexcept {
Registry::Get().Record(m_region, seconds);
}
void Region::AddCount(const std::uint64_t work_units) const noexcept {
Registry::Get().AddCount(m_region, work_units);
}
ScopedTimer::ScopedTimer(const Region &region) noexcept
: m_region(region),
m_start(std::chrono::steady_clock::now()) {
}
ScopedTimer::~ScopedTimer() noexcept {
const auto stop = std::chrono::steady_clock::now();
m_region.Record(std::chrono::duration<double>(stop - m_start).count());
}
} // namespace mean_field::profiling

View File

@@ -0,0 +1,248 @@
module;
#include <algorithm>
#include <cmath>
#include <numbers>
#include <optional>
#include <stdexcept>
#include <vector>
#include <mfem.hpp>
module mean_field;
import :seed.lane_emden;
import :utils.misc;
namespace {
struct LaneEmdenPoint final {
double coordinate{0.0};
double value{0.0};
double derivative{0.0};
};
struct LaneEmdenDerivative final {
double value{0.0};
double derivative{0.0};
};
[[nodiscard]] LaneEmdenDerivative evaluate_lane_emden_rhs(
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)
};
}
[[nodiscard]] LaneEmdenPoint take_lane_emden_step(
const LaneEmdenPoint &point,
const double step,
const double polytropicIndex
) {
const LaneEmdenDerivative first =
evaluate_lane_emden_rhs(point.coordinate, point.value, point.derivative, polytropicIndex);
const LaneEmdenDerivative second = evaluate_lane_emden_rhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * first.value,
point.derivative + 0.5 * step * first.derivative, polytropicIndex
);
const LaneEmdenDerivative third = evaluate_lane_emden_rhs(
point.coordinate + 0.5 * step, point.value + 0.5 * step * second.value,
point.derivative + 0.5 * step * second.derivative, polytropicIndex
);
const LaneEmdenDerivative fourth = evaluate_lane_emden_rhs(
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)
};
}
[[nodiscard]] std::vector<LaneEmdenPoint> solve_lane_emden(
const double polytropicIndex,
const double coordinateLimit,
const double integrationStep
) {
if (!std::isfinite(polytropicIndex) || polytropicIndex < 0.0) {
throw std::invalid_argument("Lane-Emden integration requires a finite, nonnegative polytropic index.");
}
if (!std::isfinite(coordinateLimit) || coordinateLimit <= 0.0) {
throw std::invalid_argument("The Lane-Emden coordinate limit must be finite and positive.");
}
if (!std::isfinite(integrationStep) || integrationStep <= 0.0) {
throw std::invalid_argument("The Lane-Emden integration step must be finite and positive.");
}
constexpr int maximumStepCount = 2'000'000;
if (std::ceil(coordinateLimit / integrationStep) > static_cast<double>(maximumStepCount)) {
throw std::invalid_argument("The requested Lane-Emden interval exceeds the integration step limit.");
}
const double initialCoordinate = std::min(1.0e-6, coordinateLimit);
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<LaneEmdenPoint> 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 && point.coordinate < coordinateLimit; ++stepIndex) {
const double step = std::min(integrationStep, coordinateLimit - point.coordinate);
LaneEmdenPoint nextPoint = take_lane_emden_step(point, step, polytropicIndex);
if (!std::isfinite(nextPoint.value)) {
throw std::runtime_error(
"The Lane-Emden integration produced a non-finite solution before reaching its termination."
);
}
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;
}
if (point.coordinate < coordinateLimit) {
throw std::runtime_error("The Lane-Emden integration exceeded its step limit.");
}
return solution;
}
[[nodiscard]] double interpolate_lane_emden_value(
const std::vector<LaneEmdenPoint> &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
namespace mean_field::seed {
DimensionlessLaneEmdenSolution integrateLaneEmden(
const double polytropicIndex,
const double coordinateLimit,
const double integrationStep
) {
const std::vector<LaneEmdenPoint> points = solve_lane_emden(polytropicIndex, coordinateLimit, integrationStep);
DimensionlessLaneEmdenSolution solution{
.coordinate = mfem::Vector(static_cast<int>(points.size())),
.theta = mfem::Vector(static_cast<int>(points.size())),
.thetaDerivative = mfem::Vector(static_cast<int>(points.size())),
.firstZeroCoordinate = std::nullopt
};
for (int index = 0; index < static_cast<int>(points.size()); ++index) {
solution.coordinate(index) = points[static_cast<std::size_t>(index)].coordinate;
solution.theta(index) = points[static_cast<std::size_t>(index)].value;
solution.thetaDerivative(index) = points[static_cast<std::size_t>(index)].derivative;
}
if (points.back().value == 0.0) {
solution.firstZeroCoordinate = points.back().coordinate;
}
return solution;
}
RadialProfile generateLaneEmdenProfile(
const eos::Polytrope &equationOfState,
const dimensions::DensityValue centralDensity,
const int radialSampleCount
) {
if (!std::isfinite(centralDensity.value()) || centralDensity.value() <= 0.0) {
throw std::invalid_argument("A Lane-Emden seed central density must be finite and positive.");
}
if (radialSampleCount < 2) {
throw std::invalid_argument("A Lane-Emden seed requires at least two radial samples.");
}
const double polytropicIndex = equationOfState.polytropic_index();
if (!std::isfinite(polytropicIndex) || polytropicIndex < 1.0 || polytropicIndex >= 5.0) {
throw std::invalid_argument("Lane-Emden seeds require a finite-radius polytrope with 1 <= n < 5.");
}
constexpr double seedCoordinateLimit = 2'000.0;
constexpr double integrationStep = 1.0e-3;
const std::vector solution = solve_lane_emden(polytropicIndex, seedCoordinateLimit, integrationStep);
if (solution.back().value != 0.0) {
throw std::runtime_error("The Lane-Emden integration did not reach its first zero within the step limit.");
}
const double surfaceCoordinate = solution.back().coordinate;
const dimensions::SpecificEnthalpyValue centralEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(equationOfState, centralDensity);
const double radialScaleSquared = centralEnthalpy.value() / (4.0 * std::numbers::pi_v<double> *
mean_field::utils::G * centralDensity.value());
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);
RadialProfile profile{
.radius = mfem::Vector(radialSampleCount),
.density = mfem::Vector(radialSampleCount),
.specificEnthalpy = mfem::Vector(radialSampleCount),
.stellarRadius = dimensions::LengthValue{radialScale * surfaceCoordinate},
.centralDensity = centralDensity,
.centralSpecificEnthalpy = centralEnthalpy
};
std::size_t interpolationIndex = 0;
for (int sampleIndex = 0; sampleIndex < radialSampleCount; ++sampleIndex) {
const double fraction = static_cast<double>(sampleIndex) / static_cast<double>(radialSampleCount - 1);
const double dimensionlessRadius = fraction * surfaceCoordinate;
const double laneEmdenValue =
interpolate_lane_emden_value(solution, dimensionlessRadius, interpolationIndex);
const dimensions::DensityValue density{centralDensity.value() * std::pow(laneEmdenValue, polytropicIndex)};
profile.radius(sampleIndex) = radialScale * dimensionlessRadius;
profile.density(sampleIndex) = density.value();
profile.specificEnthalpy(sampleIndex) =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(equationOfState, density).value();
}
profile.radius(0) = 0.0;
profile.density(0) = centralDensity.value();
profile.specificEnthalpy(0) = centralEnthalpy.value();
const int surfaceIndex = radialSampleCount - 1;
profile.radius(surfaceIndex) = profile.stellarRadius.value();
profile.density(surfaceIndex) = 0.0;
profile.specificEnthalpy(surfaceIndex) = 0.0;
return profile;
}
} // namespace mean_field::seed

View File

@@ -0,0 +1,219 @@
module;
#include <algorithm>
#include <cmath>
#include <limits>
#include <numbers>
#include <stdexcept>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :field.mfem;
import :seed.stellar_equilibrium_projection;
import :utils.domain;
import :utils.misc;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
void validate_profile(const mean_field::seed::RadialProfile &profile) {
const int sampleCount = profile.radius.Size();
if (sampleCount < 2 || profile.density.Size() != sampleCount ||
profile.specificEnthalpy.Size() != sampleCount) {
throw std::invalid_argument("A radial seed projection requires equally sized profiles with two samples.");
}
if (!std::isfinite(profile.stellarRadius.value()) || profile.stellarRadius.value() <= 0.0 ||
!std::isfinite(profile.centralDensity.value()) || profile.centralDensity.value() <= 0.0 ||
!std::isfinite(profile.centralSpecificEnthalpy.value()) || profile.centralSpecificEnthalpy.value() <= 0.0) {
throw std::invalid_argument("A radial seed projection requires finite, positive physical scales.");
}
for (int index = 0; index < sampleCount; ++index) {
if (!std::isfinite(profile.radius(index)) || !std::isfinite(profile.density(index)) ||
!std::isfinite(profile.specificEnthalpy(index)) || profile.density(index) < 0.0 ||
profile.specificEnthalpy(index) < 0.0) {
throw std::invalid_argument("A radial seed projection received a non-finite or negative profile.");
}
if (index > 0 && profile.radius(index) <= profile.radius(index - 1)) {
throw std::invalid_argument("A radial seed projection requires strictly increasing radii.");
}
}
const int surfaceIndex = sampleCount - 1;
const double radialScale = std::max(profile.stellarRadius.value(), 1.0);
if (std::abs(profile.radius(0)) > 64.0 * std::numeric_limits<double>::epsilon() * radialScale ||
std::abs(profile.radius(surfaceIndex) - profile.stellarRadius.value()) >
64.0 * std::numeric_limits<double>::epsilon() * radialScale ||
profile.density(0) != profile.centralDensity.value() ||
profile.specificEnthalpy(0) != profile.centralSpecificEnthalpy.value() ||
profile.density(surfaceIndex) != 0.0 || profile.specificEnthalpy(surfaceIndex) != 0.0) {
throw std::invalid_argument("A radial seed projection received inconsistent center or surface metadata.");
}
}
[[nodiscard]] double interpolate_profile(
const mfem::Vector &radius,
const mfem::Vector &values,
const double requestedRadius
) {
if (requestedRadius <= radius(0)) {
return values(0);
}
const int finalIndex = radius.Size() - 1;
if (requestedRadius >= radius(finalIndex)) {
return values(finalIndex);
}
int lowerIndex = 0;
int upperIndex = finalIndex;
while (upperIndex - lowerIndex > 1) {
const int middleIndex = lowerIndex + (upperIndex - lowerIndex) / 2;
if (radius(middleIndex) <= requestedRadius) {
lowerIndex = middleIndex;
} else {
upperIndex = middleIndex;
}
}
const double fraction = (requestedRadius - radius(lowerIndex)) / (radius(upperIndex) - radius(lowerIndex));
return (1.0 - fraction) * values(lowerIndex) + fraction * values(upperIndex);
}
struct SurfaceRadiusRange final {
double minimum;
double maximum;
};
[[nodiscard]] SurfaceRadiusRange measure_surface_radius(const mean_field::fem::FEM &finiteElementModel) {
if (finiteElementModel.surfaceDeformationFes == nullptr) {
throw std::invalid_argument("Radial seed projection requires the surface-deformation space.");
}
mfem::ParFiniteElementSpace &surfaceSpace = *finiteElementModel.surfaceDeformationFes;
const mean_field::field::ScalarBoundaryDofMap surfaceMap =
mean_field::field::make_stellar_surface_scalar_dof_map<DomainSchema>(surfaceSpace);
mfem::Vector radiusSquared(surfaceMap.local_size());
radiusSquared = 0.0;
mfem::ParGridFunction coordinateField(&surfaceSpace);
for (int component = 0; component < surfaceSpace.GetMesh()->SpaceDimension(); ++component) {
mfem::FunctionCoefficient coordinateCoefficient([component](const mfem::Vector &position) {
return position(component);
});
coordinateField.ProjectCoefficient(coordinateCoefficient);
mfem::Vector coordinateTrue;
coordinateField.GetTrueDofs(coordinateTrue);
const mfem::Vector surfaceCoordinate = surfaceMap.gather(coordinateTrue);
for (int index = 0; index < radiusSquared.Size(); ++index) {
radiusSquared(index) += surfaceCoordinate(index) * surfaceCoordinate(index);
}
}
double localMinimum = std::numeric_limits<double>::infinity();
double localMaximum = 0.0;
for (int index = 0; index < radiusSquared.Size(); ++index) {
const double radius = std::sqrt(radiusSquared(index));
localMinimum = std::min(localMinimum, radius);
localMaximum = std::max(localMaximum, radius);
}
double globalMinimum = 0.0;
double globalMaximum = 0.0;
MPI_Allreduce(&localMinimum, &globalMinimum, 1, MPI_DOUBLE, MPI_MIN, surfaceSpace.GetComm());
MPI_Allreduce(&localMaximum, &globalMaximum, 1, MPI_DOUBLE, MPI_MAX, surfaceSpace.GetComm());
if (!std::isfinite(globalMinimum) || !std::isfinite(globalMaximum) || globalMinimum <= 0.0 ||
globalMaximum < globalMinimum) {
throw std::runtime_error("The stellar surface has no finite, positive radial extent.");
}
return {.minimum = globalMinimum, .maximum = globalMaximum};
}
} // namespace
namespace mean_field::seed::detail {
ProjectedRadialFields projectRadialFields(
fem::FEM &finiteElementModel,
const RadialProfile &profile,
const dimensions::MassValue targetMass,
const StellarEquilibriumProjectionOptions &options
) {
validate_profile(profile);
if (!std::isfinite(options.surfaceRadiusRelativeTolerance) || options.surfaceRadiusRelativeTolerance < 0.0) {
throw std::invalid_argument("The surface-radius projection tolerance must be finite and nonnegative.");
}
const SurfaceRadiusRange surfaceRadius = measure_surface_radius(finiteElementModel);
const double targetRadius = profile.stellarRadius.value();
const double comparisonScale = std::max({targetRadius, surfaceRadius.maximum, 1.0e-300});
const double relativeMismatch =
std::max(std::abs(surfaceRadius.minimum - targetRadius), std::abs(surfaceRadius.maximum - targetRadius)) /
comparisonScale;
if (relativeMismatch > options.surfaceRadiusRelativeTolerance) {
throw std::invalid_argument(
"The radial seed surface does not coincide with the spherical reference discretization."
);
}
if (finiteElementModel.densityFes == nullptr || finiteElementModel.enthalpyFes == nullptr ||
finiteElementModel.displacementFes == nullptr || finiteElementModel.gravityFluxFes == nullptr ||
finiteElementModel.gravityPotentialFes == nullptr) {
throw std::invalid_argument("Radial seed projection requires the complete equilibrium discretization.");
}
mfem::FunctionCoefficient densityCoefficient([&profile](const mfem::Vector &position) {
return interpolate_profile(profile.radius, profile.density, position.Norml2());
});
mfem::FunctionCoefficient enthalpyCoefficient([&profile](const mfem::Vector &position) {
return interpolate_profile(profile.radius, profile.specificEnthalpy, position.Norml2());
});
mfem::ParGridFunction densityField(finiteElementModel.densityFes.get());
mfem::ParGridFunction enthalpyField(finiteElementModel.enthalpyFes.get());
mfem::ParGridFunction displacementField(finiteElementModel.displacementFes.get());
densityField = 0.0;
enthalpyField = 0.0;
displacementField = 0.0;
densityField.ProjectCoefficient(densityCoefficient);
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
const physics::GravitySolution gravitySolution =
physics::solve_gravity_field(finiteElementModel, options.gravity, densityField, displacementField);
double radialMomentIntegral = 0.0;
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
const double leftRadius = profile.radius(index);
const double rightRadius = profile.radius(index + 1);
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
radialMomentIntegral +=
0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
}
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
throw std::runtime_error("The radial profile has no finite, positive moment of inertia.");
}
const field::FieldDofGridFunctionAdapter densityAdapter =
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*finiteElementModel.densityFes);
const field::FieldDofGridFunctionAdapter enthalpyAdapter =
field::make_field_dof_grid_function_adapter<field::Enthalpy, DomainSchema>(*finiteElementModel.enthalpyFes);
const field::FieldDofGridFunctionAdapter gravityFluxAdapter =
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(
*finiteElementModel.gravityFluxFes
);
const field::FieldDofGridFunctionAdapter gravityPotentialAdapter =
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(
*finiteElementModel.gravityPotentialFes
);
return {
.density = densityAdapter.gather(densityField),
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
.sphericalMomentOfInertia = sphericalMomentOfInertia
};
}
} // namespace mean_field::seed::detail

View File

@@ -0,0 +1,638 @@
module;
#include <algorithm>
#include <chrono>
#include <cmath>
#include <complex>
#include <cstdint>
#include <limits>
#include <memory>
#include <ranges>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#include <Eigen/SVD>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :solver.preconditioning_diagnostics;
namespace {
using Clock = std::chrono::steady_clock;
[[nodiscard]] double seconds_between(
const Clock::time_point start,
const Clock::time_point finish
) {
return std::chrono::duration<double>(finish - start).count();
}
void verify_finite_vector(
const mfem::Vector &vector,
const char *message
) {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
throw std::invalid_argument(message);
}
}
}
[[nodiscard]] double global_dot(
const mfem::Vector &left,
const mfem::Vector &right,
const MPI_Comm communicator
) {
if (communicator == MPI_COMM_NULL) {
throw std::invalid_argument("Preconditioning diagnostics require a valid MPI communicator.");
}
if (left.Size() != right.Size()) {
throw std::invalid_argument("A distributed inner product received vectors with different sizes.");
}
const double localValue = left * right;
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, communicator);
return globalValue;
}
[[nodiscard]] double global_norm(
const mfem::Vector &vector,
const MPI_Comm communicator
) {
return std::sqrt(std::max(global_dot(vector, vector, communicator), 0.0));
}
[[nodiscard]] mean_field::solver::OperatorApplicationStatistics maximum_rank_statistics(
const mean_field::solver::OperatorApplicationStatistics &local,
const MPI_Comm communicator
) {
unsigned long long localApplications = static_cast<unsigned long long>(local.applications);
unsigned long long maximumApplications{0};
MPI_Allreduce(&localApplications, &maximumApplications, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
mean_field::solver::OperatorApplicationStatistics result;
result.applications = static_cast<std::uint64_t>(maximumApplications);
MPI_Allreduce(&local.totalSeconds, &result.totalSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local.maximumSeconds, &result.maximumSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] double maximum_rank_value(
const double localValue,
const MPI_Comm communicator
) {
double result = 0.0;
MPI_Allreduce(&localValue, &result, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] mean_field::solver::PreconditionerLifecycleStatistics maximum_rank_lifecycle_statistics(
const mean_field::solver::PreconditionerLifecycleStatistics &local,
const MPI_Comm communicator
) {
unsigned long long localSetups = static_cast<unsigned long long>(local.setups);
unsigned long long localRefreshes = static_cast<unsigned long long>(local.refreshes);
unsigned long long maximumSetups{0};
unsigned long long maximumRefreshes{0};
MPI_Allreduce(&localSetups, &maximumSetups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&localRefreshes, &maximumRefreshes, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
mean_field::solver::PreconditionerLifecycleStatistics result;
result.setups = static_cast<std::uint64_t>(maximumSetups);
result.refreshes = static_cast<std::uint64_t>(maximumRefreshes);
MPI_Allreduce(&local.setupSeconds, &result.setupSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local.refreshSeconds, &result.refreshSeconds, 1, MPI_DOUBLE, MPI_MAX, communicator);
return result;
}
[[nodiscard]] Eigen::MatrixXd copy_hessenberg(
const Eigen::MatrixXd &source,
const int rowCount,
const int columnCount
) {
return source.topLeftCorner(rowCount, columnCount);
}
} // namespace
namespace mean_field::solver {
InstrumentedOperator::InstrumentedOperator(const mfem::Operator &operation)
: mfem::Operator(
operation.Height(),
operation.Width()
),
m_operation(std::addressof(operation)) {
}
void InstrumentedOperator::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
const Clock::time_point start = Clock::now();
m_operation->Mult(input, output);
const double elapsed = seconds_between(start, Clock::now());
++m_statistics.applications;
m_statistics.totalSeconds += elapsed;
m_statistics.maximumSeconds = std::max(m_statistics.maximumSeconds, elapsed);
}
void InstrumentedOperator::ResetStatistics() const noexcept {
m_statistics = {};
}
const OperatorApplicationStatistics &InstrumentedOperator::GetStatistics() const noexcept {
return m_statistics;
}
const mfem::Operator &InstrumentedOperator::GetOperation() const noexcept {
return *m_operation;
}
InstrumentedPreconditioner::InstrumentedPreconditioner(mfem::Solver &preconditioner)
: mfem::Solver(
preconditioner.Height(),
preconditioner.Width(),
preconditioner.iterative_mode
),
m_preconditioner(std::addressof(preconditioner)) {
}
void InstrumentedPreconditioner::SetOperator(const mfem::Operator &operation) {
const Clock::time_point start = Clock::now();
m_preconditioner->SetOperator(operation);
m_lifecycleStatistics.setupSeconds += seconds_between(start, Clock::now());
++m_lifecycleStatistics.setups;
if (m_preconditioner->Height() != Height() || m_preconditioner->Width() != Width()) {
throw std::invalid_argument("An instrumented preconditioner changed dimensions during SetOperator.");
}
}
void InstrumentedPreconditioner::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
const Clock::time_point start = Clock::now();
m_preconditioner->Mult(input, output);
const double elapsed = seconds_between(start, Clock::now());
++m_statistics.applications;
m_statistics.totalSeconds += elapsed;
m_statistics.maximumSeconds = std::max(m_statistics.maximumSeconds, elapsed);
}
void InstrumentedPreconditioner::ResetStatistics() const noexcept {
m_statistics = {};
}
const OperatorApplicationStatistics &InstrumentedPreconditioner::GetStatistics() const noexcept {
return m_statistics;
}
const PreconditionerLifecycleStatistics &InstrumentedPreconditioner::GetLifecycleStatistics() const noexcept {
return m_lifecycleStatistics;
}
const mfem::Solver &InstrumentedPreconditioner::GetPreconditioner() const noexcept {
return *m_preconditioner;
}
IdentityPreconditioner::IdentityPreconditioner(const int size) : mfem::Solver(size) {
if (size <= 0) {
throw std::invalid_argument("An identity preconditioner requires a positive dimension.");
}
}
void IdentityPreconditioner::SetOperator(const mfem::Operator &operation) {
if (operation.Height() != Height() || operation.Width() != Width()) {
throw std::invalid_argument("The identity preconditioner received an incompatible operator.");
}
}
void IdentityPreconditioner::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
if (input.Size() != Width()) {
throw std::invalid_argument("The identity preconditioner received an input with the wrong size.");
}
output = input;
}
FixedRightPreconditionedOperator::FixedRightPreconditionedOperator(
const mfem::Operator &jacobian,
const mfem::Solver &inversePreconditioner
)
: mfem::Operator(
jacobian.Height(),
inversePreconditioner.Width()
),
m_jacobian(std::addressof(jacobian)),
m_inversePreconditioner(std::addressof(inversePreconditioner)),
m_preconditionedDirection(inversePreconditioner.Height()) {
if (jacobian.Height() != jacobian.Width()) {
throw std::invalid_argument("A preconditioned stellar Jacobian must be square.");
}
if (inversePreconditioner.Height() != jacobian.Width() || inversePreconditioner.Width() != jacobian.Height()) {
throw std::invalid_argument("The inverse preconditioner does not map residuals into Jacobian states.");
}
if (Height() != Width()) {
throw std::invalid_argument("The fixed right-preconditioned product must be square.");
}
}
void FixedRightPreconditionedOperator::Mult(
const mfem::Vector &input,
mfem::Vector &output
) const {
if (input.Size() != Width()) {
throw std::invalid_argument("The right-preconditioned operator received an input with the wrong size.");
}
m_inversePreconditioner->Mult(input, m_preconditionedDirection);
m_jacobian->Mult(m_preconditionedDirection, output);
}
const mfem::Operator &FixedRightPreconditionedOperator::GetJacobian() const noexcept {
return *m_jacobian;
}
const mfem::Solver &FixedRightPreconditionedOperator::GetInversePreconditioner() const noexcept {
return *m_inversePreconditioner;
}
void ResidualHistoryMonitor::Reset() {
mfem::IterativeSolverMonitor::Reset();
m_history.clear();
}
void ResidualHistoryMonitor::MonitorResidual(
const int iteration,
const double norm,
const mfem::Vector &,
const bool final
) {
m_history.push_back({.iteration = iteration, .reportedNorm = norm, .final = final});
}
const std::vector<IterationResidualMeasurement> &ResidualHistoryMonitor::GetHistory() const noexcept {
return m_history;
}
DirectResidualMeasurement measureDirectResidual(
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
const std::span<const operators::RootBlockDescriptor> residualBlocks,
const MPI_Comm communicator,
const double denominatorFloor
) {
if (jacobian.Height() != jacobian.Width() || rightHandSide.Size() != jacobian.Height() ||
solution.Size() != jacobian.Width()) {
throw std::invalid_argument("Direct residual measurement received incompatible linear-system dimensions.");
}
if (!std::isfinite(denominatorFloor) || denominatorFloor <= 0.0) {
throw std::invalid_argument("The direct-residual denominator floor must be finite and positive.");
}
verify_finite_vector(rightHandSide, "Direct residual measurement received a non-finite right-hand side.");
verify_finite_vector(solution, "Direct residual measurement received a non-finite solution.");
int expectedOffset = 0;
for (const operators::RootBlockDescriptor &block : residualBlocks) {
if (block.kind != operators::RootBlockKind::residual || block.offset != expectedOffset || block.size < 0 ||
block.offset + block.size > jacobian.Height() || !std::isfinite(block.scale) || block.scale <= 0.0) {
throw std::invalid_argument("Residual block descriptors do not form the canonical equation layout.");
}
expectedOffset += block.size;
}
if (expectedOffset != jacobian.Height()) {
throw std::invalid_argument("Residual block descriptors do not cover the complete equation vector.");
}
mfem::Vector action(jacobian.Height());
jacobian.Mult(solution, action);
if (action.Size() != rightHandSide.Size()) {
throw std::runtime_error("The Jacobian returned an action with the wrong size.");
}
mfem::Vector trueResidual(rightHandSide);
trueResidual -= action;
verify_finite_vector(trueResidual, "Direct residual measurement produced a non-finite residual.");
DirectResidualMeasurement measurement;
measurement.rightHandSideNorm = global_norm(rightHandSide, communicator);
measurement.trueResidualNorm = global_norm(trueResidual, communicator);
const double denominator = std::max(measurement.rightHandSideNorm, denominatorFloor);
measurement.relativeResidual = measurement.trueResidualNorm / denominator;
measurement.blocks.reserve(residualBlocks.size());
for (const operators::RootBlockDescriptor &block : residualBlocks) {
const mfem::Vector blockRightHandSide(
const_cast<mfem::real_t *>(rightHandSide.GetData()) + block.offset, block.size
);
const mfem::Vector blockResidual(trueResidual.GetData() + block.offset, block.size);
const double blockRightHandSideNorm = global_norm(blockRightHandSide, communicator);
const double blockResidualNorm = global_norm(blockResidual, communicator);
const double blockDenominator = std::max(blockRightHandSideNorm, denominatorFloor);
const double globalResidualFraction =
measurement.trueResidualNorm > denominatorFloor
? blockResidualNorm * blockResidualNorm /
(measurement.trueResidualNorm * measurement.trueResidualNorm)
: 0.0;
measurement.blocks.push_back(
{.stableId = std::string(block.stableId),
.size = block.size,
.descriptorScale = block.scale,
.rightHandSideNorm = blockRightHandSideNorm,
.trueResidualNorm = blockResidualNorm,
.blockRelativeResidual = blockResidualNorm / blockDenominator,
.scaledRightHandSideNorm = blockRightHandSideNorm / block.scale,
.scaledTrueResidualNorm = blockResidualNorm / block.scale,
.contributionToGlobalRelativeResidual = blockResidualNorm / denominator,
.fractionOfGlobalSquaredResidualNorm = globalResidualFraction}
);
}
return measurement;
}
LinearSolveMeasurement measureLinearSolve(
const mfem::IterativeSolver &iterativeSolver,
const mfem::Operator &jacobian,
const mfem::Vector &rightHandSide,
const mfem::Vector &solution,
const std::span<const operators::RootBlockDescriptor> residualBlocks,
const OperatorApplicationStatistics &jacobianStatistics,
const OperatorApplicationStatistics &inversePreconditionerStatistics,
const PreconditionerLifecycleStatistics &inversePreconditionerLifecycle,
const ResidualHistoryMonitor &monitor,
const double localSolveSeconds,
const MPI_Comm communicator,
const double denominatorFloor
) {
if (!std::isfinite(localSolveSeconds) || localSolveSeconds < 0.0) {
throw std::invalid_argument("A linear-solve duration must be finite and nonnegative.");
}
const DirectResidualMeasurement directResidual =
measureDirectResidual(jacobian, rightHandSide, solution, residualBlocks, communicator, denominatorFloor);
const double reportedInitial = iterativeSolver.GetInitialNorm();
const double reportedFinal = iterativeSolver.GetFinalNorm();
const double reportedReduction =
std::abs(reportedInitial) > denominatorFloor ? std::abs(reportedFinal) / std::abs(reportedInitial) : 0.0;
double digitsPerJacobianApplication = 0.0;
if (jacobianStatistics.applications > 0 && directResidual.relativeResidual >= 0.0 &&
std::isfinite(directResidual.relativeResidual)) {
digitsPerJacobianApplication = -std::log10(std::max(directResidual.relativeResidual, denominatorFloor)) /
static_cast<double>(jacobianStatistics.applications);
}
return {
.solverConverged = iterativeSolver.GetConverged(),
.outerIterations = iterativeSolver.GetNumIterations(),
.solverReportedInitialNorm = reportedInitial,
.solverReportedFinalNorm = reportedFinal,
.solverReportedResidualReduction = reportedReduction,
.trueResidualDigitsReducedPerJacobianApplication = digitsPerJacobianApplication,
.solveSecondsMaximumRank = maximum_rank_value(localSolveSeconds, communicator),
.jacobian = maximum_rank_statistics(jacobianStatistics, communicator),
.inversePreconditioner = maximum_rank_statistics(inversePreconditionerStatistics, communicator),
.inversePreconditionerLifecycle =
maximum_rank_lifecycle_statistics(inversePreconditionerLifecycle, communicator),
.directResidual = directResidual,
.reportedResidualHistory = monitor.GetHistory()
};
}
ArnoldiSpectralMeasurement measureArnoldiSpectrum(
const mfem::Operator &operation,
const mfem::Vector &initialDirection,
const MPI_Comm communicator,
const ArnoldiOptions &options
) {
if (operation.Height() != operation.Width() || operation.Width() <= 0) {
throw std::invalid_argument("Arnoldi diagnostics require a nonempty square operator.");
}
if (initialDirection.Size() != operation.Width()) {
throw std::invalid_argument("The Arnoldi initial direction has the wrong size.");
}
if (options.krylovDimension <= 0 || !std::isfinite(options.breakdownRelativeTolerance) ||
options.breakdownRelativeTolerance < 0.0 || !std::isfinite(options.ritzConvergenceRelativeTolerance) ||
options.ritzConvergenceRelativeTolerance < 0.0) {
throw std::invalid_argument("Arnoldi diagnostic options are invalid.");
}
verify_finite_vector(initialDirection, "Arnoldi diagnostics received a non-finite initial direction.");
const Clock::time_point measurementStart = Clock::now();
OperatorApplicationStatistics localApplicationStatistics;
const double initialNorm = global_norm(initialDirection, communicator);
if (!std::isfinite(initialNorm) || initialNorm <= 0.0) {
throw std::invalid_argument("Arnoldi diagnostics require a nonzero initial direction.");
}
const int requestedDimension = std::min(options.krylovDimension, operation.Width());
Eigen::MatrixXd hessenberg = Eigen::MatrixXd::Zero(requestedDimension + 1, requestedDimension);
std::vector<mfem::Vector> basis;
basis.reserve(static_cast<std::size_t>(requestedDimension + 1));
basis.emplace_back(initialDirection);
basis.back() /= initialNorm;
int achievedDimension{0};
bool invariantSubspaceFound{false};
for (int column = 0; column < requestedDimension; ++column) {
mfem::Vector candidate(operation.Height());
const Clock::time_point applicationStart = Clock::now();
operation.Mult(basis[static_cast<std::size_t>(column)], candidate);
const double applicationSeconds = seconds_between(applicationStart, Clock::now());
++localApplicationStatistics.applications;
localApplicationStatistics.totalSeconds += applicationSeconds;
localApplicationStatistics.maximumSeconds =
std::max(localApplicationStatistics.maximumSeconds, applicationSeconds);
if (candidate.Size() != operation.Height()) {
throw std::runtime_error("The Arnoldi operator returned a vector with the wrong size.");
}
verify_finite_vector(candidate, "The Arnoldi operator produced a non-finite vector.");
const double unorthogonalizedNorm = global_norm(candidate, communicator);
const int passCount = options.reorthogonalize ? 2 : 1;
for (int pass = 0; pass < passCount; ++pass) {
for (int row = 0; row <= column; ++row) {
const double projection = global_dot(basis[static_cast<std::size_t>(row)], candidate, communicator);
hessenberg(row, column) += projection;
candidate.Add(-projection, basis[static_cast<std::size_t>(row)]);
}
}
const double nextNorm = global_norm(candidate, communicator);
hessenberg(column + 1, column) = nextNorm;
achievedDimension = column + 1;
const double breakdownScale = std::max(unorthogonalizedNorm, 1.0);
if (nextNorm <= options.breakdownRelativeTolerance * breakdownScale) {
invariantSubspaceFound = true;
break;
}
if (column + 1 < requestedDimension) {
candidate /= nextNorm;
basis.push_back(std::move(candidate));
}
}
if (achievedDimension <= 0) {
throw std::runtime_error("Arnoldi diagnostics did not construct a Krylov projection.");
}
const Eigen::MatrixXd projected = copy_hessenberg(hessenberg, achievedDimension, achievedDimension);
const Eigen::MatrixXd projectedRectangular =
copy_hessenberg(hessenberg, achievedDimension + 1, achievedDimension);
Eigen::EigenSolver<Eigen::MatrixXd> eigenSolver(projected, true);
if (eigenSolver.info() != Eigen::Success) {
throw std::runtime_error("The projected Arnoldi eigenproblem did not converge.");
}
Eigen::JacobiSVD<Eigen::MatrixXd> singularValueDecomposition(projectedRectangular);
if (singularValueDecomposition.info() != Eigen::Success) {
throw std::runtime_error("The projected Arnoldi singular-value problem did not converge.");
}
ArnoldiSpectralMeasurement measurement;
measurement.requestedDimension = requestedDimension;
measurement.achievedDimension = achievedDimension;
measurement.invariantSubspaceFound = invariantSubspaceFound;
const OperatorApplicationStatistics globalApplicationStatistics =
maximum_rank_statistics(localApplicationStatistics, communicator);
measurement.operatorApplications = globalApplicationStatistics.applications;
measurement.operatorApplicationSecondsMaximumRank = globalApplicationStatistics.totalSeconds;
measurement.operatorMaximumApplicationSecondsMaximumRank = globalApplicationStatistics.maximumSeconds;
measurement.ritzValues.reserve(static_cast<std::size_t>(achievedDimension));
const Eigen::VectorXd singularValues = singularValueDecomposition.singularValues();
measurement.projectedLargestSingularValue = singularValues(0);
measurement.projectedSmallestSingularValue = singularValues(singularValues.size() - 1);
measurement.projectedConditionProxy =
measurement.projectedSmallestSingularValue > 0.0
? measurement.projectedLargestSingularValue / measurement.projectedSmallestSingularValue
: std::numeric_limits<double>::infinity();
const double finalSubdiagonal = hessenberg(achievedDimension, achievedDimension - 1);
std::complex<double> centroid{0.0, 0.0};
const auto eigenvalues = eigenSolver.eigenvalues();
const auto eigenvectors = eigenSolver.eigenvectors();
for (int index = 0; index < achievedDimension; ++index) {
const std::complex<double> eigenvalue = eigenvalues(index);
const double eigenvectorNorm = eigenvectors.col(index).norm();
const double residualEstimate =
eigenvectorNorm > 0.0
? std::abs(finalSubdiagonal * eigenvectors(achievedDimension - 1, index)) / eigenvectorNorm
: std::numeric_limits<double>::infinity();
const double convergenceScale = std::max(std::abs(eigenvalue), 1.0);
const double relativeResidualEstimate = residualEstimate / convergenceScale;
const bool converged = relativeResidualEstimate <= options.ritzConvergenceRelativeTolerance;
measurement.ritzValues.push_back(
{.realPart = eigenvalue.real(),
.imaginaryPart = eigenvalue.imag(),
.magnitude = std::abs(eigenvalue),
.distanceFromOne = std::abs(eigenvalue - std::complex<double>{1.0, 0.0}),
.residualEstimate = residualEstimate,
.relativeResidualEstimate = relativeResidualEstimate,
.converged = converged}
);
centroid += eigenvalue;
measurement.convergedRitzValueCount += converged ? 1 : 0;
measurement.negativeRealPartCount += eigenvalue.real() < 0.0 ? 1 : 0;
}
centroid /= static_cast<double>(achievedDimension);
measurement.centroidRealPart = centroid.real();
measurement.centroidImaginaryPart = centroid.imag();
measurement.minimumMagnitude = std::numeric_limits<double>::infinity();
measurement.minimumRealPart = std::numeric_limits<double>::infinity();
measurement.maximumRealPart = -std::numeric_limits<double>::infinity();
double squaredDistanceFromOne{0.0};
double squaredClusterRadius{0.0};
for (const RitzValueMeasurement &ritz : measurement.ritzValues) {
const std::complex<double> value{ritz.realPart, ritz.imaginaryPart};
measurement.minimumMagnitude = std::min(measurement.minimumMagnitude, ritz.magnitude);
measurement.maximumMagnitude = std::max(measurement.maximumMagnitude, ritz.magnitude);
measurement.minimumRealPart = std::min(measurement.minimumRealPart, ritz.realPart);
measurement.maximumRealPart = std::max(measurement.maximumRealPart, ritz.realPart);
measurement.maximumAbsoluteImaginaryPart =
std::max(measurement.maximumAbsoluteImaginaryPart, std::abs(ritz.imaginaryPart));
squaredDistanceFromOne += ritz.distanceFromOne * ritz.distanceFromOne;
squaredClusterRadius += std::norm(value - centroid);
double pairDefect = std::numeric_limits<double>::infinity();
for (const RitzValueMeasurement &candidate : measurement.ritzValues) {
pairDefect = std::min(
pairDefect,
std::abs(std::complex<double>{candidate.realPart, candidate.imaginaryPart} - std::conj(value))
);
}
measurement.conjugatePairDefect = std::max(measurement.conjugatePairDefect, pairDefect);
}
measurement.rmsDistanceFromOne = std::sqrt(squaredDistanceFromOne / achievedDimension);
measurement.rmsClusterRadius = std::sqrt(squaredClusterRadius / achievedDimension);
const double projectedFrobeniusSquared = projected.squaredNorm();
if (projectedFrobeniusSquared > 0.0) {
const Eigen::MatrixXd normalityCommutator =
projected.transpose() * projected - projected * projected.transpose();
measurement.projectedDepartureFromNormality = normalityCommutator.norm() / projectedFrobeniusSquared;
}
const Eigen::MatrixXd hermitianPart = 0.5 * (projected + projected.transpose());
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> fieldOfValuesSolver(hermitianPart);
if (fieldOfValuesSolver.info() != Eigen::Success) {
throw std::runtime_error("The projected field-of-values problem did not converge.");
}
measurement.projectedFieldOfValuesMinimumRealPart = fieldOfValuesSolver.eigenvalues().minCoeff();
measurement.projectedFieldOfValuesMaximumRealPart = fieldOfValuesSolver.eigenvalues().maxCoeff();
const double localMeasurementSeconds = seconds_between(measurementStart, Clock::now());
const double localNonApplicationSeconds =
std::max(localMeasurementSeconds - localApplicationStatistics.totalSeconds, 0.0);
measurement.measurementSecondsMaximumRank = maximum_rank_value(localMeasurementSeconds, communicator);
measurement.nonApplicationSecondsMaximumRank = maximum_rank_value(localNonApplicationSeconds, communicator);
return measurement;
}
std::vector<RitzValueMeasurement> selectRitzValues(
const ArnoldiSpectralMeasurement &measurement,
const RitzValueOrdering ordering,
const int count
) {
if (count < 0) {
throw std::invalid_argument("The requested Ritz-value count must be nonnegative.");
}
std::vector<RitzValueMeasurement> selected;
selected.reserve(measurement.ritzValues.size());
for (const RitzValueMeasurement &value : measurement.ritzValues) {
if (value.converged) {
selected.push_back(value);
}
}
std::ranges::sort(selected, [ordering](const RitzValueMeasurement &left, const RitzValueMeasurement &right) {
switch (ordering) {
case RitzValueOrdering::closest_to_zero:
return left.magnitude < right.magnitude;
case RitzValueOrdering::farthest_from_one:
return left.distanceFromOne > right.distanceFromOne;
case RitzValueOrdering::smallest_real_part:
return left.realPart < right.realPart;
case RitzValueOrdering::largest_magnitude:
return left.magnitude > right.magnitude;
}
return false;
});
if (static_cast<int>(selected.size()) > count) {
selected.resize(static_cast<std::size_t>(count));
}
return selected;
}
} // namespace mean_field::solver

View File

@@ -12,6 +12,9 @@ namespace mean_field::utils {
) {
const int dim = fem.mesh->Dimension();
x_ref = x_phys_target;
mapping::GridFunctionMappingEvaluator mapping_evaluator(
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
);
mfem::Array<int> init_elem;
mfem::Array<mfem::IntegrationPoint> init_ip;
@@ -29,17 +32,17 @@ namespace mean_field::utils {
mfem::Array<mfem::IntegrationPoint> 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) {
mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]);
T0->SetIntPoint(&origin_ip[0]);
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim);
fem.mapping->ComputeJacobian(*T0, J0);
mfem::CalcInverse(J0, J0_inv);
mapping::MappingPointContext context;
MFEM_VERIFY(
mapping_evaluator.EvaluatePoint(*T0, origin_ip[0], context) == mapping::MappingStatus::valid,
"Reference-point initialization encountered an invalid mapping."
);
J0_inv.Mult(x_phys_target, x_ref);
context.inverse_mapping_jacobian.Mult(x_phys_target, x_ref);
}
init_P.SetCol(0, x_ref);
@@ -72,9 +75,6 @@ namespace mean_field::utils {
mfem::Vector residual(dim);
mfem::Vector step(dim);
mfem::DenseMatrix J_map(dim, dim);
mfem::DenseMatrix J_map_inv(dim, dim);
int find_failures = 0;
for (int iter = 0; iter < max_iter; ++iter) {
@@ -98,12 +98,14 @@ 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);
fem.mapping->GetPhysicalPoint(*T, ip, current_x_phys);
mapping::MappingPointContext context;
if (mapping_evaluator.EvaluatePoint(*T, ip, context) != mapping::MappingStatus::valid) {
return false;
}
const mfem::Vector &current_x_phys = context.physical_position;
for (int i = 0; i < dim; ++i) {
residual(i) = current_x_phys(i) - x_phys_target(i);
@@ -113,9 +115,7 @@ namespace mean_field::utils {
return true;
}
fem.mapping->ComputeJacobian(*T, J_map);
mfem::CalcInverse(J_map, J_map_inv);
J_map_inv.Mult(residual, step);
context.inverse_mapping_jacobian.Mult(residual, step);
double alpha = 1.0;
mfem::Vector x_ref_candidate(dim);
@@ -160,8 +160,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 +176,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 +187,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;
}

View File

@@ -1,130 +1,21 @@
module;
#include <expected>
#include <mfem.hpp>
module mean_field;
import :boundary.contexts;
namespace mean_field::utils {
DOMAINS operator|(
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(
static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)
);
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
DOMAINS operator&(
DOMAINS lhs,
DOMAINS rhs
) {
return static_cast<DOMAINS>(
static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)
);
}
void populate_element_mask(
const mfem::Mesh *mesh,
const DOMAINS domain,
mfem::Array<int> &mask
) {
const int max_attr = mesh->attributes.Max();
mask.SetSize(max_attr);
mask = 0;
if ((domain & DOMAINS::CORE) == DOMAINS::CORE && max_attr >= 1) {
mask[0] = 1;
}
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE &&
max_attr >= 2) {
mask[1] = 1;
}
if ((domain & DOMAINS::VACUUM) == DOMAINS::VACUUM && max_attr >= 3) {
mask[2] = 1;
}
}
void populate_domain_tdofs(
const mfem::ParFiniteElementSpace *fes,
const mfem::Array<int> &element_mask,
mfem::Array<int> &ess_tdof
) {
mfem::Array<int> vdof_marker(fes->GetVSize());
vdof_marker = 0;
for (int i = 0; i < fes->GetMesh()->GetNE(); i++) {
const int attr = fes->GetMesh()->GetAttribute(i);
if (element_mask[attr - 1]) {
mfem::Array<int> dofs;
fes->GetElementVDofs(i, dofs);
for (int j = 0; j < dofs.Size(); j++) {
int index = dofs[j];
if (index < 0)
index = -1 - index;
vdof_marker[index] = 1;
}
}
}
fes->MarkerToList(vdof_marker, ess_tdof);
}
std::expected<
boundary::Bounds,
boundary::BoundsError>
discover_bounds(
const mfem::Mesh *mesh,
const int vacuum_attr
) {
double local_min_r = std::numeric_limits<double>::max();
double local_max_r = -std::numeric_limits<double>::max();
bool found_vacuum = false;
for (int i = 0; i < mesh->GetNE(); ++i) {
if (mesh->GetAttribute(i) == vacuum_attr) {
found_vacuum = true;
mfem::Array<int> vertices;
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]
);
local_min_r = std::min(local_min_r, r);
local_max_r = std::max(local_max_r, r);
}
}
}
double global_min_r, global_max_r;
int global_found_vacuum;
int l_found = found_vacuum ? 1 : 0;
MPI_Comm comm = MPI_COMM_WORLD;
if (const auto *pmesh = dynamic_cast<const mfem::ParMesh *>(mesh)) {
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
);
if (global_found_vacuum) {
return boundary::Bounds(global_min_r, global_max_r);
}
return std::unexpected(boundary::BoundsError::CANNOT_FIND_VACUUM);
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
int get_mesh_order(const mfem::Mesh &mesh) {

View File

@@ -1,182 +1,162 @@
#pragma once
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <map>
#include <mutex>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <mpi.h>
#ifndef MEAN_FIELD_ENABLE_PROFILING
#define MEAN_FIELD_ENABLE_PROFILING 0
#endif
namespace mean_field::profiling {
struct Statistics {
unsigned long long observations{0};
unsigned long long warmups{0};
unsigned long long samples{0};
unsigned long long warmup_target{0};
std::uint64_t observations{0};
std::uint64_t warmups{0};
std::uint64_t samples{0};
std::uint64_t warmup_target{0};
std::uint64_t work_units{0};
double total_seconds{0.0};
double minimum_seconds{std::numeric_limits<double>::infinity()};
double minimum_seconds{0.0};
double maximum_seconds{0.0};
};
struct DistributedStatistics {
std::string label;
std::uint64_t minimum_samples{0};
std::uint64_t maximum_samples{0};
std::uint64_t maximum_warmups{0};
std::uint64_t minimum_work_units{0};
std::uint64_t maximum_work_units{0};
double maximum_rank_average_seconds{0.0};
double global_minimum_seconds{0.0};
double global_maximum_seconds{0.0};
double maximum_rank_total_seconds{0.0};
};
class Registry {
public:
static Registry& Get() {
static Registry registry;
return registry;
}
static Registry &Get();
void Record(const std::string& label, const double seconds, const unsigned long long warmup_count) {
std::scoped_lock lock(m_mutex);
Statistics& statistics = m_statistics[label];
Registry(const Registry &) = delete;
Registry &operator=(const Registry &) = delete;
Registry(Registry &&) = delete;
Registry &operator=(Registry &&) = delete;
statistics.warmup_target = std::max(statistics.warmup_target, warmup_count);
const bool is_warmup = statistics.observations < statistics.warmup_target;
++statistics.observations;
~Registry();
if (is_warmup) {
++statistics.warmups;
return;
}
void Record(
std::string_view label,
double seconds,
std::uint64_t warmup_count = 0
);
++statistics.samples;
statistics.total_seconds += seconds;
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
}
void AddCount(
std::string_view label,
std::uint64_t work_units
);
void Reset() {
std::scoped_lock lock(m_mutex);
m_statistics.clear();
}
void Reset();
void Print(MPI_Comm communicator) const {
const std::map<std::string, Statistics> snapshot = GetSnapshot();
[[nodiscard]] std::map<
std::string,
Statistics,
std::less<>>
Snapshot() const;
int mpi_initialized = 0;
int mpi_finalized = 0;
MPI_Initialized(&mpi_initialized);
if (mpi_initialized) MPI_Finalized(&mpi_finalized);
[[nodiscard]] std::vector<DistributedStatistics> Aggregate(MPI_Comm communicator) const;
const bool use_mpi = mpi_initialized && !mpi_finalized;
int rank = 0;
int communicator_size = 1;
void Print(
MPI_Comm communicator,
std::ostream &stream
) const;
if (use_mpi) {
MPI_Comm_rank(communicator, &rank);
MPI_Comm_size(communicator, &communicator_size);
}
void Print(MPI_Comm communicator) const;
if (rank == 0) {
std::cout << '\n';
std::cout << std::left << std::setw(42) << "Profile Region"
<< std::right << std::setw(11) << "Samples"
<< std::setw(10) << "Warmups"
<< std::setw(14) << "Avg Max ms"
<< std::setw(14) << "Min ms"
<< std::setw(14) << "Max ms"
<< std::setw(14) << "Total Max s" << '\n';
std::cout << std::string(119, '-') << '\n';
}
for (const auto& [label, local_statistics] : snapshot) {
unsigned long long minimum_samples = local_statistics.samples;
unsigned long long maximum_samples = local_statistics.samples;
unsigned long long maximum_warmups = local_statistics.warmups;
double local_average = local_statistics.samples > 0 ? local_statistics.total_seconds / static_cast<double>(local_statistics.samples) : 0.0;
double local_minimum = local_statistics.samples > 0 ? local_statistics.minimum_seconds : std::numeric_limits<double>::infinity();
double local_maximum = local_statistics.maximum_seconds;
double local_total = local_statistics.total_seconds;
double maximum_rank_average = local_average;
double global_minimum = local_minimum;
double global_maximum = local_maximum;
double maximum_rank_total = local_total;
if (use_mpi) {
MPI_Allreduce(&local_statistics.samples, &minimum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MIN, communicator);
MPI_Allreduce(&local_statistics.samples, &maximum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&local_statistics.warmups, &maximum_warmups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
MPI_Allreduce(&local_average, &maximum_rank_average, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local_minimum, &global_minimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
MPI_Allreduce(&local_maximum, &global_maximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
MPI_Allreduce(&local_total, &maximum_rank_total, 1, MPI_DOUBLE, MPI_MAX, communicator);
}
if (!std::isfinite(global_minimum)) global_minimum = 0.0;
if (rank == 0) {
const std::string sample_string = minimum_samples == maximum_samples
? std::to_string(minimum_samples)
: std::to_string(minimum_samples) + "-" + std::to_string(maximum_samples);
std::cout << std::left << std::setw(100) << label
<< std::right << std::setw(11) << sample_string
<< std::setw(10) << maximum_warmups
<< std::setw(14) << std::fixed << std::setprecision(3) << 1.0e3 * maximum_rank_average
<< std::setw(14) << 1.0e3 * global_minimum
<< std::setw(14) << 1.0e3 * global_maximum
<< std::setw(14) << std::setprecision(6) << maximum_rank_total << '\n';
}
}
if (rank == 0) {
std::cout << std::string(119, '=') << '\n';
std::cout << "MPI ranks: " << communicator_size << "\n\n";
}
}
void PrintCsv(
MPI_Comm communicator,
std::ostream &stream
) const;
private:
[[nodiscard]] std::map<std::string, Statistics> GetSnapshot() const {
std::scoped_lock lock(m_mutex);
return m_statistics;
}
friend class Region;
Registry();
[[nodiscard]] std::size_t Register(
std::string_view label,
std::uint64_t warmup_count
);
void Record(
std::size_t region,
double seconds
) noexcept;
void AddCount(
std::size_t region,
std::uint64_t work_units
) noexcept;
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class Region {
public:
explicit Region(
std::string_view label,
std::uint64_t warmup_count = 0
);
void Record(double seconds) const noexcept;
void AddCount(std::uint64_t work_units) const noexcept;
private:
mutable std::mutex m_mutex;
std::map<std::string, Statistics> m_statistics;
std::size_t m_region;
};
class ScopedTimer {
public:
ScopedTimer(std::string label, const unsigned long long warmup_count)
: m_label(std::move(label)),
m_warmup_count(warmup_count),
m_start(std::chrono::steady_clock::now()) {}
explicit ScopedTimer(const Region &region) noexcept;
ScopedTimer(const ScopedTimer&) = delete;
ScopedTimer& operator=(const ScopedTimer&) = delete;
ScopedTimer(ScopedTimer&&) = delete;
ScopedTimer& operator=(ScopedTimer&&) = delete;
ScopedTimer(const ScopedTimer &) = delete;
ScopedTimer &operator=(const ScopedTimer &) = delete;
ScopedTimer(ScopedTimer &&) = delete;
ScopedTimer &operator=(ScopedTimer &&) = delete;
~ScopedTimer() {
try {
const auto stop = std::chrono::steady_clock::now();
const double seconds = std::chrono::duration<double>(stop - m_start).count();
Registry::Get().Record(m_label, seconds, m_warmup_count);
} catch (...) {}
}
~ScopedTimer() noexcept;
private:
std::string m_label;
unsigned long long m_warmup_count;
const Region &m_region;
std::chrono::steady_clock::time_point m_start;
};
}
} // namespace mean_field::profiling
#define MEAN_FIELD_PROFILE_JOIN_IMPL(left, right) left##right
#define MEAN_FIELD_PROFILE_JOIN(left, right) MEAN_FIELD_PROFILE_JOIN_IMPL(left, right)
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, __COUNTER__)(label, warmup_count)
#if MEAN_FIELD_ENABLE_PROFILING
#define MEAN_FIELD_PROFILE_SCOPE(label) \
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
#define MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, identifier) \
static const ::mean_field::profiling::Region MEAN_FIELD_PROFILE_JOIN(mean_field_profile_region_, identifier)( \
label, warmup_count \
); \
const ::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, identifier)( \
MEAN_FIELD_PROFILE_JOIN(mean_field_profile_region_, identifier) \
)
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, __COUNTER__)
#define MEAN_FIELD_PROFILE_SCOPE(label) MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
do { \
@@ -184,11 +164,53 @@ namespace mean_field::profiling {
__VA_ARGS__; \
} while (false)
#define MEAN_FIELD_PROFILE_CALL(label, ...) \
MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_CALL(label, ...) MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_RESET() \
::mean_field::profiling::Registry::Get().Reset()
#define MEAN_FIELD_PROFILE_EVALUATE_IMPL(label, warmup_count, identifier, ...) \
([&]() -> decltype(auto) { \
MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, identifier); \
return (__VA_ARGS__); \
}())
#define MEAN_FIELD_PROFILE_PRINT(communicator) \
::mean_field::profiling::Registry::Get().Print(communicator)
#define MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, warmup_count, ...) \
MEAN_FIELD_PROFILE_EVALUATE_IMPL(label, warmup_count, __COUNTER__, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_EVALUATE(label, ...) MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, 1, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_COUNT_IMPL(label, work_units, identifier) \
do { \
static const ::mean_field::profiling::Region MEAN_FIELD_PROFILE_JOIN(mean_field_profile_counter_, identifier)( \
label \
); \
MEAN_FIELD_PROFILE_JOIN(mean_field_profile_counter_, identifier).AddCount(work_units); \
} while (false)
#define MEAN_FIELD_PROFILE_COUNT(label, work_units) MEAN_FIELD_PROFILE_COUNT_IMPL(label, work_units, __COUNTER__)
#define MEAN_FIELD_PROFILE_RESET() ::mean_field::profiling::Registry::Get().Reset()
#define MEAN_FIELD_PROFILE_PRINT(communicator) ::mean_field::profiling::Registry::Get().Print(communicator)
#define MEAN_FIELD_PROFILE_PRINT_CSV(communicator, stream) \
::mean_field::profiling::Registry::Get().PrintCsv(communicator, stream)
#else
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) ((void)0)
#define MEAN_FIELD_PROFILE_SCOPE(label) ((void)0)
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
do { \
__VA_ARGS__; \
} while (false)
#define MEAN_FIELD_PROFILE_CALL(label, ...) MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
#define MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, warmup_count, ...) (__VA_ARGS__)
#define MEAN_FIELD_PROFILE_EVALUATE(label, ...) (__VA_ARGS__)
#define MEAN_FIELD_PROFILE_COUNT(label, work_units) ((void)0)
#define MEAN_FIELD_PROFILE_RESET() ((void)0)
#define MEAN_FIELD_PROFILE_PRINT(communicator) ((void)0)
#define MEAN_FIELD_PROFILE_PRINT_CSV(communicator, stream) ((void)0)
#endif

View File

@@ -12,8 +12,7 @@ export namespace mean_field::analysis {
const fem::FEM &fem,
const mfem::GridFunction &gf,
utils::DOMAINS domain = utils::DOMAINS::ALL,
mapping::COORDINATE_SPACE coord_space =
mapping::COORDINATE_SPACE::PHYSICAL
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,
mapping::COORDINATE_SPACE coordinate_space = mapping::COORDINATE_SPACE::PHYSICAL,
utils::DOMAINS domain = utils::DOMAINS::STELLAR
);
} // namespace mean_field::analysis

View File

@@ -15,9 +15,7 @@ export namespace mean_field::boundary {
Boundaries b,
const int a
) {
return static_cast<int>(
static_cast<uint8_t>(b) - static_cast<uint8_t>(a)
);
return static_cast<int>(static_cast<uint8_t>(b) - static_cast<uint8_t>(a));
}
struct Bounds {

View File

@@ -0,0 +1,111 @@
module;
#include <cstdint>
#include <string_view>
export module mean_field:deformation.descriptors;
export namespace mean_field::deformation {
enum class SurfaceMotionKind : std::uint8_t { Radial, Normal, GeneralVector };
enum class GeometricGaugeTreatment : std::uint8_t {
Retained,
ExcludedByParameterization,
ConstrainedByPrescription
};
enum class InteriorCenterBehavior : std::uint8_t { Unspecified, FixedAtReferenceCenter, DeterminedBySurfaceMotion };
enum class VacuumOuterBoundaryBehavior : std::uint8_t {
Unspecified,
FixedAtReferenceInfinity,
DeterminedBySurfaceMotion
};
struct SurfaceDeformationDescriptor final {
std::string_view name;
int spatialDimension;
SurfaceMotionKind motionKind;
bool linearOnReferenceGeometry;
bool requiresStarShapedReferenceSurface;
bool hasExactDerivativeTranspose;
bool hasExactPullbackDerivative;
GeometricGaugeTreatment translationTreatment;
GeometricGaugeTreatment orientationTreatment;
[[nodiscard]] constexpr bool isValid() const noexcept {
return !name.empty() && spatialDimension > 0;
}
[[nodiscard]] constexpr bool supportsExactNewtonLinearization() const noexcept {
return hasExactDerivativeTranspose && hasExactPullbackDerivative;
}
constexpr bool operator==(const SurfaceDeformationDescriptor &) const = default;
};
struct InteriorDeformationExtensionDescriptor final {
std::string_view name;
int spatialDimension;
bool linearOnReferenceGeometry;
bool requiresRadialFoliation;
bool requiresAuxiliarySolve;
bool hasExactDerivativeTranspose;
bool hasExactPullbackDerivative;
InteriorCenterBehavior centerBehavior;
[[nodiscard]] constexpr bool isValid() const noexcept {
return !name.empty() && spatialDimension > 0 && centerBehavior != InteriorCenterBehavior::Unspecified;
}
[[nodiscard]] constexpr bool supportsExactNewtonLinearization() const noexcept {
return hasExactDerivativeTranspose && hasExactPullbackDerivative;
}
constexpr bool operator==(const InteriorDeformationExtensionDescriptor &) const = default;
};
struct VacuumDeformationExtensionDescriptor final {
std::string_view name;
int spatialDimension;
bool linearOnReferenceGeometry;
bool requiresRadialFoliation;
bool requiresAuxiliarySolve;
bool hasExactDerivativeTranspose;
bool hasExactPullbackDerivative;
VacuumOuterBoundaryBehavior outerBoundaryBehavior;
[[nodiscard]] constexpr bool isValid() const noexcept {
return !name.empty() && spatialDimension > 0 &&
outerBoundaryBehavior != VacuumOuterBoundaryBehavior::Unspecified;
}
[[nodiscard]] constexpr bool supportsExactNewtonLinearization() const noexcept {
return hasExactDerivativeTranspose && hasExactPullbackDerivative;
}
constexpr bool operator==(const VacuumDeformationExtensionDescriptor &) const = default;
};
struct DomainDeformationDescriptor final {
SurfaceDeformationDescriptor surfaceDeformation;
InteriorDeformationExtensionDescriptor stellarInteriorExtension;
VacuumDeformationExtensionDescriptor vacuumExtension;
bool linearOnReferenceGeometry;
bool requiresAuxiliarySolve;
bool hasExactDerivativeTranspose;
bool hasExactPullbackDerivative;
[[nodiscard]] constexpr bool isValid() const noexcept {
return surfaceDeformation.isValid() && stellarInteriorExtension.isValid() && vacuumExtension.isValid() &&
surfaceDeformation.spatialDimension == stellarInteriorExtension.spatialDimension &&
surfaceDeformation.spatialDimension == vacuumExtension.spatialDimension;
}
[[nodiscard]] constexpr bool supportsExactNewtonLinearization() const noexcept {
return hasExactDerivativeTranspose && hasExactPullbackDerivative;
}
constexpr bool operator==(const DomainDeformationDescriptor &) const = default;
};
} // namespace mean_field::deformation

View File

@@ -0,0 +1,870 @@
module;
#include <algorithm>
#include <cmath>
#include <compare>
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:deformation.domain_deformation;
export import :deformation.interior_extension;
export import :deformation.nodal_radial_surface;
export import :deformation.radial_extensions;
export import :deformation.surface_prescription;
export import :deformation.vacuum_extension;
export import :fem;
export import :field.mfem;
export import :utils.domain;
export namespace mean_field::deformation {
enum class VolumeDeformationOwner : std::uint8_t { StellarInterior, Vacuum };
struct DomainDeformationDiscretizationDependencies final {
const mfem::Mesh *physicalMeshIdentity{nullptr};
const mfem::ParMesh *logicalReferenceMeshIdentity{nullptr};
const mfem::ParFiniteElementSpace *surfaceScalarSpaceIdentity{nullptr};
const mfem::ParFiniteElementSpace *volumeDisplacementSpaceIdentity{nullptr};
long physicalMeshSequence{-1};
long logicalReferenceMeshSequence{-1};
long surfaceScalarSpaceSequence{-1};
long volumeDisplacementSpaceSequence{-1};
[[nodiscard]] bool isCurrent() const noexcept {
return physicalMeshIdentity != nullptr && logicalReferenceMeshIdentity != nullptr &&
surfaceScalarSpaceIdentity != nullptr && volumeDisplacementSpaceIdentity != nullptr &&
physicalMeshIdentity->GetSequence() == physicalMeshSequence &&
logicalReferenceMeshIdentity->GetSequence() == logicalReferenceMeshSequence &&
surfaceScalarSpaceIdentity->GetSequence() == surfaceScalarSpaceSequence &&
volumeDisplacementSpaceIdentity->GetSequence() == volumeDisplacementSpaceSequence;
}
};
struct DomainDeformationCompositionReport final {
int scalarTrueDofCount{0};
int stellarInteriorOwnedScalarDofCount{0};
int vacuumOwnedScalarDofCount{0};
int sharedSurfaceScalarDofCount{0};
[[nodiscard]] constexpr int assignedScalarDofCount() const noexcept {
return stellarInteriorOwnedScalarDofCount + vacuumOwnedScalarDofCount;
}
constexpr auto operator<=>(const DomainDeformationCompositionReport &) const = default;
};
struct DomainDeformationGeometryReport final {
double minimumJacobianDeterminant{std::numeric_limits<double>::infinity()};
[[nodiscard]] bool isOrientationPreserving(const double determinantFloor = 0.0) const noexcept {
return std::isfinite(minimumJacobianDeterminant) && std::isfinite(determinantFloor) &&
determinantFloor >= 0.0 && minimumJacobianDeterminant > determinantFloor;
}
};
struct PreparedDomainDeformationActionStatistics final {
std::uint64_t volumeBuildApplications{0};
std::uint64_t jacobianApplications{0};
std::uint64_t jacobianTransposeApplications{0};
std::uint64_t pullbackDerivativeApplications{0};
std::uint64_t geometryInspections{0};
constexpr auto operator<=>(const PreparedDomainDeformationActionStatistics &) const = default;
};
template <typename Candidate>
concept PreparedDomainDeformationOperator = requires(
const std::remove_cvref_t<Candidate> &preparedDeformation,
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &volumeDisplacement,
mfem::Vector &parameterDual
) {
{ preparedDeformation.descriptor() } noexcept -> std::same_as<DomainDeformationDescriptor>;
{ preparedDeformation.parameterCount() } noexcept -> std::same_as<int>;
{ preparedDeformation.surfaceDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedDeformation.volumeDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement) } -> std::same_as<void>;
{ preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacement) } -> std::same_as<void>;
{
preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual)
} -> std::same_as<void>;
{
preparedDeformation.applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDual
)
} -> std::same_as<void>;
};
template <
PreparedSurfaceDeformationPrescription PreparedSurface,
PreparedInteriorDeformationExtension PreparedInterior,
PreparedVacuumDeformationExtension PreparedVacuum>
class PreparedDomainDeformation final {
public:
PreparedDomainDeformation(
PreparedSurface preparedSurface,
PreparedInterior preparedInterior,
PreparedVacuum preparedVacuum,
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
)
: m_surface(std::move(preparedSurface)),
m_interior(std::move(preparedInterior)),
m_vacuum(std::move(preparedVacuum)),
m_volumeDisplacementSpace(&volumeDisplacementSpace),
m_descriptor(makeDescriptor(
m_surface,
m_interior,
m_vacuum
)),
m_surfaceDisplacementWorkspace(surfaceDisplacementSize()),
m_surfaceDirectionWorkspace(surfaceDisplacementSize()),
m_interiorVolumeWorkspace(volumeDisplacementSize()),
m_vacuumVolumeWorkspace(volumeDisplacementSize()),
m_interiorVolumeDualWorkspace(volumeDisplacementSize()),
m_vacuumVolumeDualWorkspace(volumeDisplacementSize()),
m_interiorSurfaceDualWorkspace(surfaceDisplacementSize()),
m_vacuumSurfaceDualWorkspace(surfaceDisplacementSize()),
m_surfaceDualWorkspace(surfaceDisplacementSize()),
m_interiorSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_vacuumSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_surfacePullbackWorkspace(surfaceDisplacementSize()),
m_parameterPullbackWorkspace(parameterCount()),
m_volumeGridFunctionWorkspace(std::make_unique<mfem::ParGridFunction>(&volumeDisplacementSpace)) {
validateCompatibility(surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh);
compileOwnership();
const mfem::Mesh *physicalMesh = volumeDisplacementSpace.GetMesh();
m_discretizationDependencies = {
.physicalMeshIdentity = physicalMesh,
.logicalReferenceMeshIdentity = &logicalReferenceMesh,
.surfaceScalarSpaceIdentity = &surfaceScalarSpace,
.volumeDisplacementSpaceIdentity = &volumeDisplacementSpace,
.physicalMeshSequence = physicalMesh->GetSequence(),
.logicalReferenceMeshSequence = logicalReferenceMesh.GetSequence(),
.surfaceScalarSpaceSequence = surfaceScalarSpace.GetSequence(),
.volumeDisplacementSpaceSequence = volumeDisplacementSpace.GetSequence()
};
}
PreparedDomainDeformation(const PreparedDomainDeformation &) = delete;
PreparedDomainDeformation &operator=(const PreparedDomainDeformation &) = delete;
PreparedDomainDeformation(PreparedDomainDeformation &&) noexcept = default;
PreparedDomainDeformation &operator=(PreparedDomainDeformation &&) noexcept = default;
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept {
return m_descriptor;
}
[[nodiscard]] int parameterCount() const noexcept {
return m_surface.parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
return m_surface.surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept {
return m_interior.interiorDisplacementSize();
}
[[nodiscard]] int scalarTrueDofCount() const noexcept {
return m_interior.scalarTrueDofCount();
}
[[nodiscard]] int spatialDimension() const noexcept {
return m_descriptor.surfaceDeformation.spatialDimension;
}
[[nodiscard]] VolumeDeformationOwner volumeOwner(const int scalarTrueDof) const {
requireScalarTrueDof(scalarTrueDof);
return m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)];
}
[[nodiscard]] bool isSharedSurfaceDof(const int scalarTrueDof) const {
requireScalarTrueDof(scalarTrueDof);
return m_interior.hasStellarSupport(scalarTrueDof) && m_vacuum.hasVacuumSupport(scalarTrueDof);
}
[[nodiscard]] const DomainDeformationCompositionReport &compositionReport() const noexcept {
return m_compositionReport;
}
[[nodiscard]] const DomainDeformationDiscretizationDependencies &discretizationDependencies() const noexcept {
return m_discretizationDependencies;
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept {
return m_discretizationDependencies.isCurrent();
}
[[nodiscard]] const PreparedDomainDeformationActionStatistics &actionStatistics() const noexcept {
return m_actionStatistics;
}
[[nodiscard]] const PreparedSurface &surfaceDeformationPrescription() const noexcept {
return m_surface;
}
[[nodiscard]] const PreparedInterior &stellarInteriorExtension() const noexcept {
return m_interior;
}
[[nodiscard]] const PreparedVacuum &vacuumExtension() const noexcept {
return m_vacuum;
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireVolumeSize(volumeDisplacement);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_interior.buildInteriorDisplacement(m_surfaceDisplacementWorkspace, m_interiorVolumeWorkspace);
m_vacuum.buildVacuumDisplacement(m_surfaceDisplacementWorkspace, m_vacuumVolumeWorkspace);
mergeVolumeFields(m_interiorVolumeWorkspace, m_vacuumVolumeWorkspace, volumeDisplacement);
++m_actionStatistics.volumeBuildApplications;
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireVolumeSize(volumeDisplacementDirection);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_surface.applyJacobian(parameters, parameterDirection, m_surfaceDirectionWorkspace);
m_interior.applyJacobian(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_interiorVolumeWorkspace
);
m_vacuum.applyJacobian(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_vacuumVolumeWorkspace
);
mergeVolumeFields(m_interiorVolumeWorkspace, m_vacuumVolumeWorkspace, volumeDisplacementDirection);
++m_actionStatistics.jacobianApplications;
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireVolumeSize(volumeDisplacementDual);
requireParameterSize(parameterDual);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
splitVolumeDual(volumeDisplacementDual);
applyExtensionTransposes();
m_surface.applyJacobianTranspose(parameters, m_surfaceDualWorkspace, parameterDual);
++m_actionStatistics.jacobianTransposeApplications;
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireVolumeSize(volumeDisplacementDual);
requireParameterSize(parameterDualAction);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_surface.applyJacobian(parameters, parameterDirection, m_surfaceDirectionWorkspace);
splitVolumeDual(volumeDisplacementDual);
applyExtensionTransposes();
m_interior.applyPullbackDerivative(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_interiorVolumeDualWorkspace,
m_interiorSurfacePullbackWorkspace
);
m_vacuum.applyPullbackDerivative(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_vacuumVolumeDualWorkspace,
m_vacuumSurfacePullbackWorkspace
);
addSurfaceFields(
m_interiorSurfacePullbackWorkspace, m_vacuumSurfacePullbackWorkspace, m_surfacePullbackWorkspace
);
m_surface.applyJacobianTranspose(parameters, m_surfacePullbackWorkspace, parameterDualAction);
m_surface.applyPullbackDerivative(
parameters, parameterDirection, m_surfaceDualWorkspace, m_parameterPullbackWorkspace
);
parameterDualAction += m_parameterPullbackWorkspace;
++m_actionStatistics.pullbackDerivativeApplications;
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const {
requireCurrentDiscretization();
requireVolumeSize(volumeDisplacement);
m_volumeGridFunctionWorkspace->SetFromTrueDofs(volumeDisplacement);
mfem::Mesh *mesh = m_volumeDisplacementSpace->GetMesh();
double localMinimumDeterminant = std::numeric_limits<double>::infinity();
int localGeometryIsFinite = 1;
for (int element = 0; element < mesh->GetNE(); ++element) {
mfem::ElementTransformation *transformation = mesh->GetElementTransformation(element);
const mfem::FiniteElement *finiteElement = m_volumeDisplacementSpace->GetFE(element);
// Positivity is a pointwise geometry requirement, not an
// integration-accuracy requirement. A rule only slightly
// above the displacement order can miss a narrow negative
// region of the determinant even when a downstream physics
// rule samples it. The determinant of a d-dimensional
// degree-p deformation gradient can vary at substantially
// higher order, so inspect at a conservative d*p scale.
const int geometryInspectionOrder =
std::max(finiteElement->GetOrder() + 2, 2 * spatialDimension() * finiteElement->GetOrder());
const mfem::IntegrationRule &rule =
mfem::IntRules.Get(transformation->GetGeometryType(), geometryInspectionOrder);
for (int point = 0; point < rule.GetNPoints(); ++point) {
transformation->SetIntPoint(&rule.IntPoint(point));
mfem::DenseMatrix deformationGradient;
m_volumeGridFunctionWorkspace->GetVectorGradient(*transformation, deformationGradient);
for (int component = 0; component < spatialDimension(); ++component) {
deformationGradient(component, component) += 1.0;
}
const double determinant = deformationGradient.Det();
if (!std::isfinite(determinant)) {
localGeometryIsFinite = 0;
} else {
localMinimumDeterminant = std::min(localMinimumDeterminant, determinant);
}
}
}
double globalMinimumDeterminant = 0.0;
int globalGeometryIsFinite = 0;
MPI_Allreduce(
&localMinimumDeterminant, &globalMinimumDeterminant, 1, MPI_DOUBLE, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
MPI_Allreduce(
&localGeometryIsFinite, &globalGeometryIsFinite, 1, MPI_INT, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
if (globalGeometryIsFinite == 0) {
globalMinimumDeterminant = std::numeric_limits<double>::quiet_NaN();
}
++m_actionStatistics.geometryInspections;
return {.minimumJacobianDeterminant = globalMinimumDeterminant};
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor = 0.0
) const {
if (!std::isfinite(determinantFloor) || determinantFloor < 0.0) {
throw std::invalid_argument("The mapped-geometry determinant floor must be finite and non-negative.");
}
buildVolumeDisplacement(parameters, volumeDisplacement);
const DomainDeformationGeometryReport report = inspectMappedGeometry(volumeDisplacement);
if (!report.isOrientationPreserving(determinantFloor)) {
throw std::domain_error("The prepared domain deformation inverts at least one volume element.");
}
return report;
}
private:
[[nodiscard]] static DomainDeformationDescriptor makeDescriptor(
const PreparedSurface &surface,
const PreparedInterior &interior,
const PreparedVacuum &vacuum
) noexcept {
const SurfaceDeformationDescriptor surfaceDescriptor = surface.descriptor();
const InteriorDeformationExtensionDescriptor interiorDescriptor = interior.descriptor();
const VacuumDeformationExtensionDescriptor vacuumDescriptor = vacuum.descriptor();
return {
.surfaceDeformation = surfaceDescriptor,
.stellarInteriorExtension = interiorDescriptor,
.vacuumExtension = vacuumDescriptor,
.linearOnReferenceGeometry = surfaceDescriptor.linearOnReferenceGeometry &&
interiorDescriptor.linearOnReferenceGeometry &&
vacuumDescriptor.linearOnReferenceGeometry,
.requiresAuxiliarySolve =
interiorDescriptor.requiresAuxiliarySolve || vacuumDescriptor.requiresAuxiliarySolve,
.hasExactDerivativeTranspose = surfaceDescriptor.hasExactDerivativeTranspose &&
interiorDescriptor.hasExactDerivativeTranspose &&
vacuumDescriptor.hasExactDerivativeTranspose,
.hasExactPullbackDerivative = surfaceDescriptor.hasExactPullbackDerivative &&
interiorDescriptor.hasExactPullbackDerivative &&
vacuumDescriptor.hasExactPullbackDerivative
};
}
void validateCompatibility(
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
) const {
const mfem::Mesh *physicalMesh = volumeDisplacementSpace.GetMesh();
if (!m_descriptor.isValid()) {
throw std::invalid_argument("Prepared domain deformation descriptors are incompatible.");
}
if (!m_descriptor.supportsExactNewtonLinearization()) {
throw std::invalid_argument("Prepared domain deformation requires exact transpose and pullback paths.");
}
if (physicalMesh == nullptr || surfaceScalarSpace.GetMesh() != physicalMesh) {
throw std::invalid_argument("Prepared domain deformation spaces must share one physical mesh.");
}
if (logicalReferenceMesh.GetNE() != physicalMesh->GetNE() ||
logicalReferenceMesh.GetNBE() != physicalMesh->GetNBE()) {
throw std::invalid_argument("Prepared domain deformation requires the paired logical reference mesh.");
}
if (m_surface.surfaceDisplacementSize() != m_interior.surfaceDisplacementSize() ||
m_surface.surfaceDisplacementSize() != m_vacuum.surfaceDisplacementSize()) {
throw std::invalid_argument("Prepared deformation factors have incompatible surface trace sizes.");
}
if (m_interior.interiorDisplacementSize() != m_vacuum.vacuumDisplacementSize() ||
m_interior.interiorDisplacementSize() != volumeDisplacementSpace.GetTrueVSize()) {
throw std::invalid_argument("Prepared deformation factors have incompatible volume vector sizes.");
}
if (m_interior.scalarTrueDofCount() != m_vacuum.scalarTrueDofCount() ||
volumeDisplacementSpace.GetTrueVSize() != spatialDimension() * m_interior.scalarTrueDofCount()) {
throw std::invalid_argument("Prepared deformation factors have incompatible scalar volume topology.");
}
if (volumeDisplacementSpace.GetOrdering() != mfem::Ordering::byNODES) {
throw std::invalid_argument("Prepared domain deformation requires MFEM byNODES volume ordering.");
}
}
void compileOwnership() {
m_volumeOwners.resize(static_cast<std::size_t>(scalarTrueDofCount()));
m_compositionReport.scalarTrueDofCount = scalarTrueDofCount();
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
const bool hasStellarSupport = m_interior.hasStellarSupport(scalarTrueDof);
const bool hasVacuumSupport = m_vacuum.hasVacuumSupport(scalarTrueDof);
if (!hasStellarSupport && !hasVacuumSupport) {
throw std::invalid_argument("A volume displacement DOF has no deformation-extension owner.");
}
if (hasStellarSupport) {
m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)] = VolumeDeformationOwner::StellarInterior;
++m_compositionReport.stellarInteriorOwnedScalarDofCount;
if (hasVacuumSupport) {
++m_compositionReport.sharedSurfaceScalarDofCount;
}
} else {
m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)] = VolumeDeformationOwner::Vacuum;
++m_compositionReport.vacuumOwnedScalarDofCount;
}
}
}
[[nodiscard]] int volumeVectorDof(
const int scalarTrueDof,
const int component
) const noexcept {
return scalarTrueDof + component * scalarTrueDofCount();
}
void mergeVolumeFields(
const mfem::Vector &interiorVolume,
const mfem::Vector &vacuumVolume,
mfem::Vector &volume
) const noexcept {
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
const mfem::Vector &source = volumeOwner(scalarTrueDof) == VolumeDeformationOwner::StellarInterior
? interiorVolume
: vacuumVolume;
for (int component = 0; component < spatialDimension(); ++component) {
const int vectorDof = volumeVectorDof(scalarTrueDof, component);
volume(vectorDof) = source(vectorDof);
}
}
}
void splitVolumeDual(const mfem::Vector &volumeDual) const noexcept {
m_interiorVolumeDualWorkspace = 0.0;
m_vacuumVolumeDualWorkspace = 0.0;
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
mfem::Vector &destination = volumeOwner(scalarTrueDof) == VolumeDeformationOwner::StellarInterior
? m_interiorVolumeDualWorkspace
: m_vacuumVolumeDualWorkspace;
for (int component = 0; component < spatialDimension(); ++component) {
const int vectorDof = volumeVectorDof(scalarTrueDof, component);
destination(vectorDof) = volumeDual(vectorDof);
}
}
}
void applyExtensionTransposes() const {
m_interior.applyJacobianTranspose(
m_surfaceDisplacementWorkspace, m_interiorVolumeDualWorkspace, m_interiorSurfaceDualWorkspace
);
m_vacuum.applyJacobianTranspose(
m_surfaceDisplacementWorkspace, m_vacuumVolumeDualWorkspace, m_vacuumSurfaceDualWorkspace
);
addSurfaceFields(m_interiorSurfaceDualWorkspace, m_vacuumSurfaceDualWorkspace, m_surfaceDualWorkspace);
}
static void addSurfaceFields(
const mfem::Vector &interior,
const mfem::Vector &vacuum,
mfem::Vector &sum
) {
sum = interior;
sum += vacuum;
}
void requireCurrentDiscretization() const {
if (!matchesCurrentDiscretization()) {
throw std::logic_error("Prepared domain deformation discretization dependencies are stale.");
}
}
void requireParameterSize(const mfem::Vector &parameters) const {
if (parameters.Size() != parameterCount()) {
throw std::invalid_argument("Prepared domain deformation received an incompatible parameter vector.");
}
}
void requireVolumeSize(const mfem::Vector &volume) const {
if (volume.Size() != volumeDisplacementSize()) {
throw std::invalid_argument("Prepared domain deformation received an incompatible volume vector.");
}
}
void requireScalarTrueDof(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared domain deformation.");
}
}
PreparedSurface m_surface;
PreparedInterior m_interior;
PreparedVacuum m_vacuum;
mfem::ParFiniteElementSpace *m_volumeDisplacementSpace;
DomainDeformationDescriptor m_descriptor;
DomainDeformationCompositionReport m_compositionReport;
DomainDeformationDiscretizationDependencies m_discretizationDependencies;
std::vector<VolumeDeformationOwner> m_volumeOwners;
mutable PreparedDomainDeformationActionStatistics m_actionStatistics;
mutable mfem::Vector m_surfaceDisplacementWorkspace;
mutable mfem::Vector m_surfaceDirectionWorkspace;
mutable mfem::Vector m_interiorVolumeWorkspace;
mutable mfem::Vector m_vacuumVolumeWorkspace;
mutable mfem::Vector m_interiorVolumeDualWorkspace;
mutable mfem::Vector m_vacuumVolumeDualWorkspace;
mutable mfem::Vector m_interiorSurfaceDualWorkspace;
mutable mfem::Vector m_vacuumSurfaceDualWorkspace;
mutable mfem::Vector m_surfaceDualWorkspace;
mutable mfem::Vector m_interiorSurfacePullbackWorkspace;
mutable mfem::Vector m_vacuumSurfacePullbackWorkspace;
mutable mfem::Vector m_surfacePullbackWorkspace;
mutable mfem::Vector m_parameterPullbackWorkspace;
mutable std::unique_ptr<mfem::ParGridFunction> m_volumeGridFunctionWorkspace;
};
template <
PreparedSurfaceDeformationPrescription PreparedSurface,
PreparedInteriorDeformationExtension PreparedInterior,
PreparedVacuumDeformationExtension PreparedVacuum>
[[nodiscard]] auto composePreparedDomainDeformation(
PreparedSurface preparedSurface,
PreparedInterior preparedInterior,
PreparedVacuum preparedVacuum,
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
) {
return PreparedDomainDeformation<PreparedSurface, PreparedInterior, PreparedVacuum>{
std::move(preparedSurface), std::move(preparedInterior), std::move(preparedVacuum),
surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh
};
}
class PreparedDomainDeformationRuntime final {
public:
template <PreparedDomainDeformationOperator PreparedDeformation>
requires(!std::same_as<
std::remove_cvref_t<PreparedDeformation>,
PreparedDomainDeformationRuntime>)
explicit PreparedDomainDeformationRuntime(PreparedDeformation &&preparedDeformation)
: m_implementation(
std::make_unique<Implementation<std::remove_cvref_t<PreparedDeformation>>>(
std::forward<PreparedDeformation>(preparedDeformation)
)
) {
}
PreparedDomainDeformationRuntime(const PreparedDomainDeformationRuntime &) = delete;
PreparedDomainDeformationRuntime &operator=(const PreparedDomainDeformationRuntime &) = delete;
PreparedDomainDeformationRuntime(PreparedDomainDeformationRuntime &&) noexcept = default;
PreparedDomainDeformationRuntime &operator=(PreparedDomainDeformationRuntime &&) noexcept = default;
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept {
return m_implementation->descriptor();
}
[[nodiscard]] int parameterCount() const noexcept {
return m_implementation->parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
return m_implementation->surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept {
return m_implementation->volumeDisplacementSize();
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept {
return m_implementation->matchesCurrentDiscretization();
}
[[nodiscard]] DomainDeformationCompositionReport compositionReport() const noexcept {
return m_implementation->compositionReport();
}
[[nodiscard]] DomainDeformationDiscretizationDependencies discretizationDependencies() const noexcept {
return m_implementation->discretizationDependencies();
}
[[nodiscard]] PreparedDomainDeformationActionStatistics actionStatistics() const noexcept {
return m_implementation->actionStatistics();
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const {
m_implementation->buildVolumeDisplacement(parameters, volumeDisplacement);
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const {
m_implementation->applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const {
m_implementation->applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const {
m_implementation->applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDualAction
);
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const {
return m_implementation->inspectMappedGeometry(volumeDisplacement);
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor = 0.0
) const {
return m_implementation->buildValidatedVolumeDisplacement(parameters, volumeDisplacement, determinantFloor);
}
private:
class Interface {
public:
virtual ~Interface() = default;
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
[[nodiscard]] virtual int surfaceDisplacementSize() const noexcept = 0;
[[nodiscard]] virtual int volumeDisplacementSize() const noexcept = 0;
[[nodiscard]] virtual bool matchesCurrentDiscretization() const noexcept = 0;
[[nodiscard]] virtual DomainDeformationCompositionReport compositionReport() const noexcept = 0;
[[nodiscard]] virtual DomainDeformationDiscretizationDependencies
discretizationDependencies() const noexcept = 0;
[[nodiscard]] virtual PreparedDomainDeformationActionStatistics actionStatistics() const noexcept = 0;
virtual void buildVolumeDisplacement(
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyJacobian(
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyJacobianTranspose(
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyPullbackDerivative(
const mfem::Vector &,
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
[[nodiscard]] virtual DomainDeformationGeometryReport inspectMappedGeometry(const mfem::Vector &) const = 0;
[[nodiscard]] virtual DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &,
mfem::Vector &,
double
) const = 0;
};
template <PreparedDomainDeformationOperator PreparedDeformation> class Implementation final : public Interface {
public:
explicit Implementation(PreparedDeformation preparedDeformation)
: m_preparedDeformation(std::move(preparedDeformation)) {
}
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept override {
return m_preparedDeformation.descriptor();
}
[[nodiscard]] int parameterCount() const noexcept override {
return m_preparedDeformation.parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept override {
return m_preparedDeformation.surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept override {
return m_preparedDeformation.volumeDisplacementSize();
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept override {
return m_preparedDeformation.matchesCurrentDiscretization();
}
[[nodiscard]] DomainDeformationCompositionReport compositionReport() const noexcept override {
return m_preparedDeformation.compositionReport();
}
[[nodiscard]] DomainDeformationDiscretizationDependencies
discretizationDependencies() const noexcept override {
return m_preparedDeformation.discretizationDependencies();
}
[[nodiscard]] PreparedDomainDeformationActionStatistics actionStatistics() const noexcept override {
return m_preparedDeformation.actionStatistics();
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const override {
m_preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement);
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const override {
m_preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const override {
m_preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const override {
m_preparedDeformation.applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDualAction
);
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const override {
return m_preparedDeformation.inspectMappedGeometry(volumeDisplacement);
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor
) const override {
return m_preparedDeformation.buildValidatedVolumeDisplacement(
parameters, volumeDisplacement, determinantFloor
);
}
private:
PreparedDeformation m_preparedDeformation;
};
std::unique_ptr<Interface> m_implementation;
};
template <
utils::domain::IsSchema SchemaT = utils::domain::CoreEnvelopeVacuumDomainSchema,
SurfaceDeformationPrescription SurfacePrescription,
InteriorDeformationExtension InteriorExtension,
VacuumDeformationExtension VacuumExtension>
requires SurfaceDeformationCompilable<
SurfacePrescription,
SurfaceDeformationCompilationContext> &&
InteriorDeformationExtensionCompilable<
InteriorExtension,
RadialDeformationExtensionCompilationContext> &&
VacuumDeformationExtensionCompilable<
VacuumExtension,
RadialDeformationExtensionCompilationContext>
[[nodiscard]] auto compileDomainDeformation(
const SurfacePrescription &surfacePrescription,
const InteriorExtension &interiorExtension,
const VacuumExtension &vacuumExtension,
fem::FEM &finiteElementModel
) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("Domain deformation compilation requires a complete finite-element model.");
}
const field::ScalarBoundaryDofMap surfaceDofMap =
field::make_stellar_surface_scalar_dof_map<SchemaT>(*finiteElementModel.surfaceDeformationFes);
const SurfaceDeformationCompilationContext surfaceContext{
*finiteElementModel.surfaceDeformationFes, surfaceDofMap
};
auto preparedSurface = compileSurfaceDeformationPrescription(surfacePrescription, surfaceContext);
const RadialDeformationExtensionCompilationContext extensionContext =
makeRadialDeformationExtensionCompilationContext<SchemaT>(
*finiteElementModel.surfaceDeformationFes, *finiteElementModel.displacementFes,
*finiteElementModel.logicalReferenceMesh
);
auto preparedInterior = compileInteriorDeformationExtension(interiorExtension, extensionContext);
auto preparedVacuum = compileVacuumDeformationExtension(vacuumExtension, extensionContext);
return composePreparedDomainDeformation(
std::move(preparedSurface), std::move(preparedInterior), std::move(preparedVacuum),
*finiteElementModel.surfaceDeformationFes, *finiteElementModel.displacementFes,
*finiteElementModel.logicalReferenceMesh
);
}
} // namespace mean_field::deformation

View File

@@ -0,0 +1,63 @@
module;
#include <concepts>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:deformation.interior_extension;
export import :deformation.descriptors;
export namespace mean_field::deformation {
template <typename Candidate>
concept PreparedInteriorDeformationExtension = requires(
const std::remove_cvref_t<Candidate> &preparedExtension,
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &interiorDisplacement,
mfem::Vector &surfaceDisplacementDual
) {
{ preparedExtension.descriptor() } noexcept -> std::same_as<InteriorDeformationExtensionDescriptor>;
{ preparedExtension.surfaceDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedExtension.interiorDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedExtension.scalarTrueDofCount() } noexcept -> std::same_as<int>;
{ preparedExtension.hasStellarSupport(0) } -> std::same_as<bool>;
{
preparedExtension.buildInteriorDisplacement(surfaceDisplacement, interiorDisplacement)
} -> std::same_as<void>;
{
preparedExtension.applyJacobian(surfaceDisplacement, surfaceDisplacementDirection, interiorDisplacement)
} -> std::same_as<void>;
{
preparedExtension.applyJacobianTranspose(
surfaceDisplacement, interiorDisplacementDual, surfaceDisplacementDual
)
} -> std::same_as<void>;
{
preparedExtension.applyPullbackDerivative(
surfaceDisplacement, surfaceDisplacementDirection, interiorDisplacementDual, surfaceDisplacementDual
)
} -> std::same_as<void>;
};
template <typename Candidate>
concept InteriorDeformationExtension = requires(const std::remove_cvref_t<Candidate> &extension) {
typename std::remove_cvref_t<Candidate>::PreparedType;
requires PreparedInteriorDeformationExtension<typename std::remove_cvref_t<Candidate>::PreparedType>;
{ extension.descriptor() } noexcept -> std::same_as<InteriorDeformationExtensionDescriptor>;
{ extension.validate() } -> std::same_as<void>;
};
template <typename Extension, typename CompilationContext>
concept InteriorDeformationExtensionCompilable =
InteriorDeformationExtension<Extension> && requires(
const std::remove_cvref_t<Extension> &extension,
const std::remove_cvref_t<CompilationContext> &context
) {
{
compileInteriorDeformationExtension(extension, context)
} -> std::same_as<typename std::remove_cvref_t<Extension>::PreparedType>;
};
} // namespace mean_field::deformation

View File

@@ -0,0 +1,138 @@
module;
#include <mfem.hpp>
export module mean_field:deformation.nodal_radial_surface;
export import :deformation.surface_prescription;
export import :field.mfem;
export namespace mean_field::deformation {
class PreparedNodalRadialSurface;
class SurfaceDeformationCompilationContext final {
public:
SurfaceDeformationCompilationContext(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
field::ScalarBoundaryDofMap surfaceDofMap
);
[[nodiscard]] mfem::ParFiniteElementSpace &scalarFiniteElementSpace() const noexcept;
[[nodiscard]] const field::ScalarBoundaryDofMap &surfaceDofMap() const noexcept;
private:
mfem::ParFiniteElementSpace *m_scalarFiniteElementSpace;
field::ScalarBoundaryDofMap m_surfaceDofMap;
};
class NodalRadialSurface final {
public:
using PreparedType = PreparedNodalRadialSurface;
explicit NodalRadialSurface(mfem::Vector referenceCenter);
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
void validate() const;
private:
mfem::Vector m_referenceCenter;
};
class PreparedNodalRadialSurface final {
public:
[[nodiscard]] SurfaceDeformationDescriptor descriptor() const noexcept;
[[nodiscard]] int parameterCount() const noexcept;
[[nodiscard]] long long globalParameterCount() const noexcept;
[[nodiscard]] long long globalParameterOffset() const noexcept;
[[nodiscard]] int spatialDimension() const noexcept;
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
[[nodiscard]] long long globalSurfaceDisplacementSize() const noexcept;
[[nodiscard]] long long globalSurfaceDisplacementOffset() const noexcept;
[[nodiscard]] int surfaceDisplacementDof(
int parameterDof,
int component
) const;
[[nodiscard]] double radialDirection(
int parameterDof,
int component
) const;
[[nodiscard]] double referenceRadius(int parameterDof) const;
[[nodiscard]] const mfem::Vector &referenceCenter() const noexcept;
[[nodiscard]] const field::ScalarBoundaryDofMap &surfaceDofMap() const noexcept;
void buildSurfaceDisplacement(
const mfem::Vector &parameters,
mfem::Vector &surfaceDisplacement
) const;
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &surfaceDisplacementDirection
) const;
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &surfaceDisplacementDual,
mfem::Vector &parameterDual
) const;
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &surfaceDisplacementDual,
mfem::Vector &parameterDualAction
) const;
private:
friend PreparedNodalRadialSurface compileSurfaceDeformationPrescription(
const NodalRadialSurface &prescription,
const SurfaceDeformationCompilationContext &context
);
PreparedNodalRadialSurface(
SurfaceDeformationDescriptor descriptor,
mfem::Vector referenceCenter,
field::ScalarBoundaryDofMap surfaceDofMap,
mfem::Vector radialDirections,
mfem::Vector referenceRadii
);
void requireParameterSize(const mfem::Vector &parameters) const;
void requireSurfaceDisplacementSize(const mfem::Vector &surfaceDisplacement) const;
SurfaceDeformationDescriptor m_descriptor;
mfem::Vector m_referenceCenter;
field::ScalarBoundaryDofMap m_surfaceDofMap;
mfem::Vector m_radialDirections;
mfem::Vector m_referenceRadii;
};
[[nodiscard]] PreparedNodalRadialSurface compileSurfaceDeformationPrescription(
const NodalRadialSurface &prescription,
const SurfaceDeformationCompilationContext &context
);
static_assert(SurfaceDeformationPrescription<NodalRadialSurface>);
static_assert(PreparedSurfaceDeformationPrescription<PreparedNodalRadialSurface>);
static_assert(SurfaceDeformationCompilable<
NodalRadialSurface,
SurfaceDeformationCompilationContext>);
} // namespace mean_field::deformation

View File

@@ -0,0 +1,302 @@
module;
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:deformation.radial_extensions;
export import :deformation.interior_extension;
export import :deformation.vacuum_extension;
export import :field.mfem;
export import :utils.domain;
export namespace mean_field::deformation {
class RadialDeformationExtensionCompilationContext final {
public:
RadialDeformationExtensionCompilationContext(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
mfem::ParFiniteElementSpace &vectorFiniteElementSpace,
mfem::ParMesh &logicalReferenceMesh,
field::ScalarBoundaryDofMap stellarSurfaceDofMap,
field::ScalarBoundaryDofMap infinitySurfaceDofMap,
mfem::Array<int> stellarMaterialMarker,
mfem::Array<int> vacuumMaterialMarker,
int stellarSurfaceBoundaryAttribute,
int infinitySurfaceBoundaryAttribute
);
[[nodiscard]] int spatialDimension() const noexcept;
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
[[nodiscard]] int volumeDisplacementSize() const noexcept;
[[nodiscard]] int scalarTrueDofCount() const noexcept;
[[nodiscard]] double logicalRadius(int scalarTrueDof) const;
[[nodiscard]] double stellarSurfaceLogicalRadius() const noexcept;
[[nodiscard]] double infinitySurfaceLogicalRadius() const noexcept;
[[nodiscard]] int surfaceInterpolationEntryCount(int scalarTrueDof) const;
[[nodiscard]] int surfaceGlobalCoordinate(
int scalarTrueDof,
int interpolationEntry
) const;
[[nodiscard]] double surfaceInterpolationWeight(
int scalarTrueDof,
int interpolationEntry
) const;
private:
friend class PreparedPowerLawRadialInteriorExtension;
friend class PreparedFixedInfinityRadialVacuumExtension;
int m_spatialDimension{0};
int m_scalarTrueDofCount{0};
int m_volumeDisplacementSize{0};
int m_surfaceDisplacementSize{0};
int m_globalSurfaceDisplacementSize{0};
int m_globalSurfaceDisplacementOffset{0};
MPI_Comm m_communicator{MPI_COMM_NULL};
mfem::Array<int> m_stellarSupport;
mfem::Array<int> m_vacuumSupport;
mfem::Vector m_logicalRadius;
double m_stellarSurfaceLogicalRadius{0.0};
double m_infinitySurfaceLogicalRadius{0.0};
std::vector<int> m_surfaceInterpolationRowOffsets;
std::vector<int> m_surfaceInterpolationGlobalCoordinates;
std::vector<double> m_surfaceInterpolationWeights;
std::vector<int> m_surfaceDisplacementCounts;
std::vector<int> m_surfaceDisplacementOffsets;
};
template <utils::domain::IsSchema SchemaT = utils::domain::CoreEnvelopeVacuumDomainSchema>
requires(
SchemaT::template contains_domain<utils::domain::Stellar>() &&
SchemaT::template contains_domain<utils::domain::Vacuum>() &&
SchemaT::template contains_boundary<utils::domain::StellarSurface>() &&
SchemaT::template contains_boundary<utils::domain::InfinitySurface>()
)
[[nodiscard]] RadialDeformationExtensionCompilationContext makeRadialDeformationExtensionCompilationContext(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
mfem::ParFiniteElementSpace &vectorFiniteElementSpace,
mfem::ParMesh &logicalReferenceMesh
) {
const mfem::Mesh *mesh = scalarFiniteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Radial deformation extension compilation requires an MFEM mesh.");
return RadialDeformationExtensionCompilationContext(
scalarFiniteElementSpace, vectorFiniteElementSpace, logicalReferenceMesh,
field::make_scalar_boundary_dof_map<utils::domain::StellarSurface, SchemaT>(scalarFiniteElementSpace),
field::make_scalar_boundary_dof_map<utils::domain::InfinitySurface, SchemaT>(scalarFiniteElementSpace),
utils::domain::make_attribute_marker<utils::domain::Stellar, SchemaT>(*mesh),
utils::domain::make_attribute_marker<utils::domain::Vacuum, SchemaT>(*mesh),
SchemaT::template boundary_attribute<utils::domain::StellarSurface>(),
SchemaT::template boundary_attribute<utils::domain::InfinitySurface>()
);
}
class PreparedPowerLawRadialInteriorExtension;
class PowerLawRadialInteriorExtension final {
public:
using PreparedType = PreparedPowerLawRadialInteriorExtension;
explicit PowerLawRadialInteriorExtension(double radialPower = 2.0);
[[nodiscard]] double radialPower() const noexcept;
[[nodiscard]] InteriorDeformationExtensionDescriptor descriptor() const noexcept;
void validate() const;
private:
double m_radialPower;
};
class PreparedPowerLawRadialInteriorExtension final {
public:
[[nodiscard]] InteriorDeformationExtensionDescriptor descriptor() const noexcept;
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
[[nodiscard]] int interiorDisplacementSize() const noexcept;
[[nodiscard]] int scalarTrueDofCount() const noexcept;
[[nodiscard]] double radialPower() const noexcept;
[[nodiscard]] bool hasStellarSupport(int scalarTrueDof) const;
[[nodiscard]] double radialWeight(int scalarTrueDof) const;
[[nodiscard]] int surfaceInterpolationEntryCount(int scalarTrueDof) const;
[[nodiscard]] int surfaceGlobalCoordinate(
int scalarTrueDof,
int interpolationEntry
) const;
[[nodiscard]] double surfaceInterpolationWeight(
int scalarTrueDof,
int interpolationEntry
) const;
void buildInteriorDisplacement(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &interiorDisplacement
) const;
void applyJacobian(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
mfem::Vector &interiorDisplacementDirection
) const;
void applyJacobianTranspose(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const;
void applyPullbackDerivative(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDualAction
) const;
private:
friend PreparedPowerLawRadialInteriorExtension compileInteriorDeformationExtension(
const PowerLawRadialInteriorExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
PreparedPowerLawRadialInteriorExtension(
const PowerLawRadialInteriorExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
void requireSurfaceSize(const mfem::Vector &surfaceDisplacement) const;
void requireInteriorSize(const mfem::Vector &interiorDisplacement) const;
void applyForward(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &interiorDisplacement
) const;
void applyTranspose(
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const;
InteriorDeformationExtensionDescriptor m_descriptor;
double m_radialPower{2.0};
int m_surfaceDisplacementSize{0};
int m_interiorDisplacementSize{0};
int m_spatialDimension{0};
int m_globalSurfaceDisplacementSize{0};
int m_globalSurfaceDisplacementOffset{0};
MPI_Comm m_communicator{MPI_COMM_NULL};
mfem::Array<int> m_stellarSupport;
mfem::Vector m_radialWeights;
std::vector<int> m_surfaceInterpolationRowOffsets;
std::vector<int> m_surfaceInterpolationGlobalCoordinates;
std::vector<double> m_surfaceInterpolationWeights;
std::vector<int> m_surfaceDisplacementCounts;
std::vector<int> m_surfaceDisplacementOffsets;
mutable mfem::Vector m_globalSurfaceDisplacementWorkspace;
mutable mfem::Vector m_localGlobalSurfaceDualWorkspace;
mutable mfem::Vector m_globalSurfaceDualWorkspace;
};
[[nodiscard]] PreparedPowerLawRadialInteriorExtension compileInteriorDeformationExtension(
const PowerLawRadialInteriorExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
class PreparedFixedInfinityRadialVacuumExtension;
class FixedInfinityRadialVacuumExtension final {
public:
using PreparedType = PreparedFixedInfinityRadialVacuumExtension;
[[nodiscard]] VacuumDeformationExtensionDescriptor descriptor() const noexcept;
void validate() const;
};
class PreparedFixedInfinityRadialVacuumExtension final {
public:
[[nodiscard]] VacuumDeformationExtensionDescriptor descriptor() const noexcept;
[[nodiscard]] int surfaceDisplacementSize() const noexcept;
[[nodiscard]] int vacuumDisplacementSize() const noexcept;
[[nodiscard]] int scalarTrueDofCount() const noexcept;
[[nodiscard]] bool hasVacuumSupport(int scalarTrueDof) const;
[[nodiscard]] double radialWeight(int scalarTrueDof) const;
[[nodiscard]] int surfaceInterpolationEntryCount(int scalarTrueDof) const;
[[nodiscard]] int surfaceGlobalCoordinate(
int scalarTrueDof,
int interpolationEntry
) const;
[[nodiscard]] double surfaceInterpolationWeight(
int scalarTrueDof,
int interpolationEntry
) const;
void buildVacuumDisplacement(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &vacuumDisplacement
) const;
void applyJacobian(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
mfem::Vector &vacuumDisplacementDirection
) const;
void applyJacobianTranspose(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const;
void applyPullbackDerivative(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDualAction
) const;
private:
friend PreparedFixedInfinityRadialVacuumExtension compileVacuumDeformationExtension(
const FixedInfinityRadialVacuumExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
PreparedFixedInfinityRadialVacuumExtension(
const FixedInfinityRadialVacuumExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
void requireSurfaceSize(const mfem::Vector &surfaceDisplacement) const;
void requireVacuumSize(const mfem::Vector &vacuumDisplacement) const;
void applyForward(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &vacuumDisplacement
) const;
void applyTranspose(
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const;
VacuumDeformationExtensionDescriptor m_descriptor;
int m_surfaceDisplacementSize{0};
int m_vacuumDisplacementSize{0};
int m_spatialDimension{0};
int m_globalSurfaceDisplacementSize{0};
int m_globalSurfaceDisplacementOffset{0};
MPI_Comm m_communicator{MPI_COMM_NULL};
mfem::Array<int> m_vacuumSupport;
mfem::Vector m_radialWeights;
std::vector<int> m_surfaceInterpolationRowOffsets;
std::vector<int> m_surfaceInterpolationGlobalCoordinates;
std::vector<double> m_surfaceInterpolationWeights;
std::vector<int> m_surfaceDisplacementCounts;
std::vector<int> m_surfaceDisplacementOffsets;
mutable mfem::Vector m_globalSurfaceDisplacementWorkspace;
mutable mfem::Vector m_localGlobalSurfaceDualWorkspace;
mutable mfem::Vector m_globalSurfaceDualWorkspace;
};
[[nodiscard]] PreparedFixedInfinityRadialVacuumExtension compileVacuumDeformationExtension(
const FixedInfinityRadialVacuumExtension &extension,
const RadialDeformationExtensionCompilationContext &context
);
static_assert(InteriorDeformationExtension<PowerLawRadialInteriorExtension>);
static_assert(PreparedInteriorDeformationExtension<PreparedPowerLawRadialInteriorExtension>);
static_assert(InteriorDeformationExtensionCompilable<
PowerLawRadialInteriorExtension,
RadialDeformationExtensionCompilationContext>);
static_assert(VacuumDeformationExtension<FixedInfinityRadialVacuumExtension>);
static_assert(PreparedVacuumDeformationExtension<PreparedFixedInfinityRadialVacuumExtension>);
static_assert(VacuumDeformationExtensionCompilable<
FixedInfinityRadialVacuumExtension,
RadialDeformationExtensionCompilationContext>);
} // namespace mean_field::deformation

View File

@@ -0,0 +1,57 @@
module;
#include <concepts>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:deformation.surface_prescription;
export import :deformation.descriptors;
export namespace mean_field::deformation {
template <typename Candidate>
concept PreparedSurfaceDeformationPrescription = requires(
const std::remove_cvref_t<Candidate> &preparedPrescription,
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &surfaceDisplacementDual,
mfem::Vector &surfaceDisplacement,
mfem::Vector &parameterDual
) {
{ preparedPrescription.descriptor() } noexcept -> std::same_as<SurfaceDeformationDescriptor>;
{ preparedPrescription.parameterCount() } noexcept -> std::same_as<int>;
{ preparedPrescription.surfaceDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedPrescription.buildSurfaceDisplacement(parameters, surfaceDisplacement) } -> std::same_as<void>;
{
preparedPrescription.applyJacobian(parameters, parameterDirection, surfaceDisplacement)
} -> std::same_as<void>;
{
preparedPrescription.applyJacobianTranspose(parameters, surfaceDisplacementDual, parameterDual)
} -> std::same_as<void>;
{
preparedPrescription.applyPullbackDerivative(
parameters, parameterDirection, surfaceDisplacementDual, parameterDual
)
} -> std::same_as<void>;
};
template <typename Candidate>
concept SurfaceDeformationPrescription = requires(const std::remove_cvref_t<Candidate> &prescription) {
typename std::remove_cvref_t<Candidate>::PreparedType;
requires PreparedSurfaceDeformationPrescription<typename std::remove_cvref_t<Candidate>::PreparedType>;
{ prescription.descriptor() } noexcept -> std::same_as<SurfaceDeformationDescriptor>;
{ prescription.validate() } -> std::same_as<void>;
};
template <typename Prescription, typename CompilationContext>
concept SurfaceDeformationCompilable =
SurfaceDeformationPrescription<Prescription> && requires(
const std::remove_cvref_t<Prescription> &prescription,
const std::remove_cvref_t<CompilationContext> &context
) {
{
compileSurfaceDeformationPrescription(prescription, context)
} -> std::same_as<typename std::remove_cvref_t<Prescription>::PreparedType>;
};
} // namespace mean_field::deformation

View File

@@ -0,0 +1,61 @@
module;
#include <concepts>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:deformation.vacuum_extension;
export import :deformation.descriptors;
export namespace mean_field::deformation {
template <typename Candidate>
concept PreparedVacuumDeformationExtension = requires(
const std::remove_cvref_t<Candidate> &preparedExtension,
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &vacuumDisplacement,
mfem::Vector &surfaceDisplacementDual
) {
{ preparedExtension.descriptor() } noexcept -> std::same_as<VacuumDeformationExtensionDescriptor>;
{ preparedExtension.surfaceDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedExtension.vacuumDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedExtension.scalarTrueDofCount() } noexcept -> std::same_as<int>;
{ preparedExtension.hasVacuumSupport(0) } -> std::same_as<bool>;
{ preparedExtension.buildVacuumDisplacement(surfaceDisplacement, vacuumDisplacement) } -> std::same_as<void>;
{
preparedExtension.applyJacobian(surfaceDisplacement, surfaceDisplacementDirection, vacuumDisplacement)
} -> std::same_as<void>;
{
preparedExtension.applyJacobianTranspose(
surfaceDisplacement, vacuumDisplacementDual, surfaceDisplacementDual
)
} -> std::same_as<void>;
{
preparedExtension.applyPullbackDerivative(
surfaceDisplacement, surfaceDisplacementDirection, vacuumDisplacementDual, surfaceDisplacementDual
)
} -> std::same_as<void>;
};
template <typename Candidate>
concept VacuumDeformationExtension = requires(const std::remove_cvref_t<Candidate> &extension) {
typename std::remove_cvref_t<Candidate>::PreparedType;
requires PreparedVacuumDeformationExtension<typename std::remove_cvref_t<Candidate>::PreparedType>;
{ extension.descriptor() } noexcept -> std::same_as<VacuumDeformationExtensionDescriptor>;
{ extension.validate() } -> std::same_as<void>;
};
template <typename Extension, typename CompilationContext>
concept VacuumDeformationExtensionCompilable =
VacuumDeformationExtension<Extension> && requires(
const std::remove_cvref_t<Extension> &extension,
const std::remove_cvref_t<CompilationContext> &context
) {
{
compileVacuumDeformationExtension(extension, context)
} -> std::same_as<typename std::remove_cvref_t<Extension>::PreparedType>;
};
} // namespace mean_field::deformation

View File

@@ -0,0 +1,312 @@
module;
#include <compare>
#include <concepts>
#include <string_view>
#include <type_traits>
export module mean_field:dimensions.quantities;
export namespace mean_field::dimensions {
/*
* QuantityValue provides semantic strong typing for scalar physical
* values expressed in the unit system selected by a model. It does not
* perform dimensional algebra or unit conversion.
*/
struct PhysicalQuantity { };
struct ThermodynamicQuantity : PhysicalQuantity { };
template <typename Candidate>
concept PhysicalQuantityType =
std::same_as<Candidate, std::remove_cv_t<Candidate>> && std::derived_from<Candidate, PhysicalQuantity>;
template <typename Candidate>
concept ThermodynamicQuantityType =
PhysicalQuantityType<Candidate> && std::derived_from<Candidate, ThermodynamicQuantity>;
namespace quantity {
struct Dimensionless final : PhysicalQuantity {
static constexpr std::string_view identifier = "dimensionless";
};
struct Mass final : PhysicalQuantity {
static constexpr std::string_view identifier = "mass";
};
struct Length final : PhysicalQuantity {
static constexpr std::string_view identifier = "length";
};
struct Time final : PhysicalQuantity {
static constexpr std::string_view identifier = "time";
};
struct Area final : PhysicalQuantity {
static constexpr std::string_view identifier = "area";
};
struct Volume final : PhysicalQuantity {
static constexpr std::string_view identifier = "volume";
};
struct Density final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "density";
};
struct SurfaceDensity final : PhysicalQuantity {
static constexpr std::string_view identifier = "surface_density";
};
struct NumberDensity final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "number_density";
};
struct Pressure final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "pressure";
};
struct Temperature final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "temperature";
};
struct Entropy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "entropy";
};
struct SpecificEntropy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_entropy";
};
struct ChemicalPotential final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "chemical_potential";
};
struct Energy final : PhysicalQuantity {
static constexpr std::string_view identifier = "energy";
};
struct InternalEnergy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "internal_energy";
};
struct SpecificEnergy final : PhysicalQuantity {
static constexpr std::string_view identifier = "specific_energy";
};
struct SpecificInternalEnergy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_internal_energy";
};
struct SpecificEnthalpy final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "specific_enthalpy";
};
struct EnergyDensity final : ThermodynamicQuantity {
static constexpr std::string_view identifier = "energy_density";
};
struct GravitationalPotential final : PhysicalQuantity {
static constexpr std::string_view identifier = "gravitational_potential";
};
struct Velocity final : PhysicalQuantity {
static constexpr std::string_view identifier = "velocity";
};
struct Acceleration final : PhysicalQuantity {
static constexpr std::string_view identifier = "acceleration";
};
struct Frequency final : PhysicalQuantity {
static constexpr std::string_view identifier = "frequency";
};
struct AngularVelocity final : PhysicalQuantity {
static constexpr std::string_view identifier = "angular_velocity";
};
struct Momentum final : PhysicalQuantity {
static constexpr std::string_view identifier = "momentum";
};
struct AngularMomentum final : PhysicalQuantity {
static constexpr std::string_view identifier = "angular_momentum";
};
struct MomentOfInertia final : PhysicalQuantity {
static constexpr std::string_view identifier = "moment_of_inertia";
};
struct Force final : PhysicalQuantity {
static constexpr std::string_view identifier = "force";
};
struct Torque final : PhysicalQuantity {
static constexpr std::string_view identifier = "torque";
};
struct Power final : PhysicalQuantity {
static constexpr std::string_view identifier = "power";
};
struct Luminosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "luminosity";
};
struct MassFlowRate final : PhysicalQuantity {
static constexpr std::string_view identifier = "mass_flow_rate";
};
struct Opacity final : PhysicalQuantity {
static constexpr std::string_view identifier = "opacity";
};
struct DynamicViscosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "dynamic_viscosity";
};
struct KinematicViscosity final : PhysicalQuantity {
static constexpr std::string_view identifier = "kinematic_viscosity";
};
struct MagneticFluxDensity final : PhysicalQuantity {
static constexpr std::string_view identifier = "magnetic_flux_density";
};
} // namespace quantity
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <PhysicalQuantityType Quantity> class QuantityValue final {
public:
explicit constexpr QuantityValue(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
[[nodiscard]] friend constexpr bool operator==(
const QuantityValue &,
const QuantityValue &
) noexcept = default;
friend constexpr QuantityValue operator+(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{lhs.m_value + rhs.m_value};
}
friend constexpr QuantityValue operator-(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{lhs.m_value - rhs.m_value};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator*(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return QuantityValue{lhs.m_value * static_cast<double>(rhs)};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator*(
const Scalar lhs,
const QuantityValue &rhs
) noexcept {
return QuantityValue{static_cast<double>(lhs) * rhs.m_value};
}
template <Numeric Scalar>
friend constexpr QuantityValue operator/(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return QuantityValue{lhs.m_value / static_cast<double>(rhs)};
}
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const QuantityValue &lhs,
const Scalar rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const Scalar lhs,
const QuantityValue &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const QuantityValue &lhs,
const QuantityValue &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
template <typename Candidate> struct IsQuantityValue : std::false_type { };
template <PhysicalQuantityType Quantity> struct IsQuantityValue<QuantityValue<Quantity>> : std::true_type { };
template <typename Candidate>
concept QuantityValueType = IsQuantityValue<std::remove_cvref_t<Candidate>>::value;
template <typename Candidate> struct QuantityOf;
template <PhysicalQuantityType Quantity> struct QuantityOf<QuantityValue<Quantity>> {
using Type = Quantity;
};
template <QuantityValueType Value> using QuantityOfT = typename QuantityOf<std::remove_cvref_t<Value>>::Type;
using DimensionlessValue = QuantityValue<quantity::Dimensionless>;
using MassValue = QuantityValue<quantity::Mass>;
using LengthValue = QuantityValue<quantity::Length>;
using TimeValue = QuantityValue<quantity::Time>;
using AreaValue = QuantityValue<quantity::Area>;
using VolumeValue = QuantityValue<quantity::Volume>;
using DensityValue = QuantityValue<quantity::Density>;
using SurfaceDensityValue = QuantityValue<quantity::SurfaceDensity>;
using NumberDensityValue = QuantityValue<quantity::NumberDensity>;
using PressureValue = QuantityValue<quantity::Pressure>;
using TemperatureValue = QuantityValue<quantity::Temperature>;
using EntropyValue = QuantityValue<quantity::Entropy>;
using SpecificEntropyValue = QuantityValue<quantity::SpecificEntropy>;
using ChemicalPotentialValue = QuantityValue<quantity::ChemicalPotential>;
using EnergyValue = QuantityValue<quantity::Energy>;
using InternalEnergyValue = QuantityValue<quantity::InternalEnergy>;
using SpecificEnergyValue = QuantityValue<quantity::SpecificEnergy>;
using SpecificInternalEnergyValue = QuantityValue<quantity::SpecificInternalEnergy>;
using SpecificEnthalpyValue = QuantityValue<quantity::SpecificEnthalpy>;
using EnergyDensityValue = QuantityValue<quantity::EnergyDensity>;
using GravitationalPotentialValue = QuantityValue<quantity::GravitationalPotential>;
using VelocityValue = QuantityValue<quantity::Velocity>;
using AccelerationValue = QuantityValue<quantity::Acceleration>;
using FrequencyValue = QuantityValue<quantity::Frequency>;
using AngularVelocityValue = QuantityValue<quantity::AngularVelocity>;
using MomentumValue = QuantityValue<quantity::Momentum>;
using AngularMomentumValue = QuantityValue<quantity::AngularMomentum>;
using MomentOfInertiaValue = QuantityValue<quantity::MomentOfInertia>;
using ForceValue = QuantityValue<quantity::Force>;
using TorqueValue = QuantityValue<quantity::Torque>;
using PowerValue = QuantityValue<quantity::Power>;
using LuminosityValue = QuantityValue<quantity::Luminosity>;
using MassFlowRateValue = QuantityValue<quantity::MassFlowRate>;
using OpacityValue = QuantityValue<quantity::Opacity>;
using DynamicViscosityValue = QuantityValue<quantity::DynamicViscosity>;
using KinematicViscosityValue = QuantityValue<quantity::KinematicViscosity>;
using MagneticFluxDensityValue = QuantityValue<quantity::MagneticFluxDensity>;
} // namespace mean_field::dimensions

View File

@@ -0,0 +1,100 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:eos.concepts;
export import :eos.relations;
export namespace mean_field::eos {
namespace detail {
template <typename EquationOfState, typename RelationType> struct ImplementsRelation : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs>
struct ImplementsRelation<
EquationOfState,
Relation<
Output,
Inputs...>> : std::bool_constant <
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState.evaluate(Relation<Output, Inputs...>{}, inputValues...)}
->std::same_as<QuantityValue<Output>>;
}>{};
template <typename EquationOfState, typename Catalog> struct ImplementsRelationCatalog : std::false_type { };
template <typename EquationOfState, typename... Relations>
struct ImplementsRelationCatalog<EquationOfState, RelationCatalog<Relations...>>
: std::bool_constant<(ImplementsRelation<EquationOfState, Relations>::value && ...)> { };
template <typename Candidate, typename = void> struct IsEquationOfStateModel : std::false_type { };
template <typename Candidate>
struct IsEquationOfStateModel<Candidate, std::void_t<typename std::remove_cvref_t<Candidate>::Relations>>
: std::bool_constant<
ValidRelationCatalog<typename std::remove_cvref_t<Candidate>::Relations> &&
ImplementsRelationCatalog<
std::remove_cvref_t<Candidate>,
typename std::remove_cvref_t<Candidate>::Relations>::value> { };
template <typename EquationOfState, typename RelationType, typename InputQuantity>
struct ImplementsPartialDerivative : std::false_type { };
template <
typename EquationOfState,
typename Output,
typename... Inputs,
typename InputQuantity>
struct ImplementsPartialDerivative<
EquationOfState,
Relation<
Output,
Inputs...>,
InputQuantity> : std::bool_constant <
(std::same_as<
InputQuantity,
Inputs> ||
...) &&
requires(
const std::remove_cvref_t<EquationOfState> &equationOfState,
QuantityValue<Inputs>... inputValues
) {
{equationOfState
.partialDerivative(Relation<Output, Inputs...>{}, WithRespectTo<InputQuantity>{}, inputValues...)}
->std::same_as<PartialDerivative<Output, InputQuantity>>;
}>{};
} // namespace detail
template <typename Candidate>
concept EquationOfStateModel = detail::IsEquationOfStateModel<Candidate>::value;
template <typename EquationOfState, typename RelationType>
concept SupportsRelation =
EquationOfStateModel<EquationOfState> && ThermodynamicRelationType<RelationType> &&
relationCatalogContains<typename std::remove_cvref_t<EquationOfState>::Relations, RelationType>;
template <typename EquationOfState, typename RelationType, typename InputQuantity>
concept SupportsPartialDerivative =
SupportsRelation<EquationOfState, RelationType> && ThermodynamicQuantityType<InputQuantity> &&
detail::ImplementsPartialDerivative<EquationOfState, RelationType, InputQuantity>::value;
template <typename Candidate>
concept StructureSeedEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, SpecificEnthalpyFromDensity>;
template <typename Candidate>
concept BarotropicClosureEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, DensityFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, DensityFromSpecificEnthalpy, dimensions::quantity::SpecificEnthalpy>;
template <typename Candidate>
concept PressureForceEquationOfState =
EquationOfStateModel<Candidate> && SupportsRelation<Candidate, PressureFromSpecificEnthalpy> &&
SupportsPartialDerivative<Candidate, PressureFromSpecificEnthalpy, dimensions::quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -0,0 +1,90 @@
module;
#include <stdexcept>
#include <string>
#include <utility>
export module mean_field:eos.evaluation;
export import :eos.concepts;
export namespace mean_field::eos {
enum class EvaluationErrorCode {
unsupported_relation,
unsupported_derivative,
wrong_input_count,
wrong_input_quantity,
nonfinite_input,
outside_domain,
nonfinite_result
};
class EvaluationError final : public std::domain_error {
public:
explicit EvaluationError(
const EvaluationErrorCode code,
std::string message
)
: std::domain_error(std::move(message)),
m_code(code) {
}
[[nodiscard]] EvaluationErrorCode code() const noexcept {
return m_code;
}
private:
EvaluationErrorCode m_code;
};
template <
ThermodynamicQuantityType OutputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsRelation<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>>
[[nodiscard]] constexpr QuantityValue<OutputQuantity> evaluate(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.evaluate(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
inputValues...
))) {
return equationOfState.evaluate(Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, inputValues...);
}
template <
ThermodynamicQuantityType OutputQuantity,
ThermodynamicQuantityType InputQuantity,
EquationOfStateModel EquationOfState,
QuantityValueType... InputValues>
requires SupportsPartialDerivative<
EquationOfState,
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>,
InputQuantity>
[[nodiscard]] constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
partialDerivative(
const EquationOfState &equationOfState,
const InputValues... inputValues
) noexcept(noexcept(equationOfState
.partialDerivative(
Relation<
OutputQuantity,
QuantityOfT<InputValues>...>{},
WithRespectTo<InputQuantity>{},
inputValues...
))) {
return equationOfState.partialDerivative(
Relation<OutputQuantity, QuantityOfT<InputValues>...>{}, WithRespectTo<InputQuantity>{}, inputValues...
);
}
} // namespace mean_field::eos

View File

@@ -0,0 +1,237 @@
module;
#include <cmath>
#include <format>
#include <stdexcept>
export module mean_field:eos.polytrope;
export import :eos.evaluation;
export namespace mean_field::eos {
class Polytrope final {
public:
struct Parameters final {
double n;
double K;
};
using Relations = RelationCatalog<
PressureFromDensity,
PressureFromSpecificEnthalpy,
SpecificEnthalpyFromDensity,
SpecificEnthalpyFromPressure,
DensityFromSpecificEnthalpy>;
explicit Polytrope(const Parameters parameters)
: Polytrope(
parameters.n,
parameters.K
) {
}
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]] dimensions::PressureValue evaluate(
PressureFromDensity,
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return dimensions::PressureValue{0.0};
}
return dimensions::PressureValue{
m_polytropic_constant * std::pow(density.value(), 1.0 + 1.0 / m_polytropic_index)
};
}
[[nodiscard]] dimensions::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromDensity,
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return dimensions::SpecificEnthalpyValue{0.0};
}
return dimensions::SpecificEnthalpyValue{
m_enthalpy_scale * std::pow(density.value(), 1.0 / m_polytropic_index)
};
}
[[nodiscard]] dimensions::DensityValue evaluate(
DensityFromSpecificEnthalpy,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() <= 0.0) {
return dimensions::DensityValue{0.0};
}
return dimensions::DensityValue{std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index)};
}
[[nodiscard]] dimensions::PressureValue evaluate(
PressureFromSpecificEnthalpy,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
const dimensions::DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
if (specificEnthalpy.value() <= 0.0) {
return dimensions::PressureValue{0.0};
}
return dimensions::PressureValue{density.value() * specificEnthalpy.value() / (m_polytropic_index + 1.0)};
}
[[nodiscard]] dimensions::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressure,
const dimensions::PressureValue pressure
) const {
validate_nonnegativity(pressure.value(), "pressure");
if (pressure.value() == 0.0) {
return dimensions::SpecificEnthalpyValue{0.0};
}
const double indexPlusOne = m_polytropic_index + 1.0;
return dimensions::SpecificEnthalpyValue{
indexPlusOne * std::pow(m_polytropic_constant, m_polytropic_index / indexPlusOne) *
std::pow(pressure.value(), 1.0 / indexPlusOne)
};
}
[[nodiscard]] PartialDerivative<
dimensions::quantity::Density,
dimensions::quantity::SpecificEnthalpy>
partialDerivative(
DensityFromSpecificEnthalpy,
WithRespectTo<dimensions::quantity::SpecificEnthalpy>,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
validate_finite(specificEnthalpy.value(), "specific enthalpy");
if (specificEnthalpy.value() < 0.0) {
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{0.0};
}
if (specificEnthalpy.value() == 0.0) {
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{
m_polytropic_index == 1.0 ? 1.0 / m_enthalpy_scale : 0.0
};
}
return PartialDerivative<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>{
m_polytropic_index / m_enthalpy_scale *
std::pow(specificEnthalpy.value() / m_enthalpy_scale, m_polytropic_index - 1.0)
};
}
[[nodiscard]] PartialDerivative<
dimensions::quantity::Pressure,
dimensions::quantity::SpecificEnthalpy>
partialDerivative(
PressureFromSpecificEnthalpy,
WithRespectTo<dimensions::quantity::SpecificEnthalpy>,
const dimensions::SpecificEnthalpyValue specificEnthalpy
) const {
const dimensions::DensityValue density = evaluate(DensityFromSpecificEnthalpy{}, specificEnthalpy);
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::SpecificEnthalpy>{
density.value()
};
}
[[nodiscard]] PartialDerivative<
dimensions::quantity::Pressure,
dimensions::quantity::Density>
partialDerivative(
PressureFromDensity,
WithRespectTo<dimensions::quantity::Density>,
const dimensions::DensityValue density
) const {
validate_nonnegativity(density.value(), "density");
if (density.value() == 0.0) {
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::Density>{0.0};
}
return PartialDerivative<dimensions::quantity::Pressure, dimensions::quantity::Density>{
m_polytropic_constant * (1.0 + 1.0 / m_polytropic_index) *
std::pow(density.value(), 1.0 / m_polytropic_index)
};
}
private:
static void validate_finite(
const double value,
const char *quantity
) {
if (!std::isfinite(value)) {
throw EvaluationError(
EvaluationErrorCode::nonfinite_input, 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 EvaluationError(
EvaluationErrorCode::outside_domain, std::format(
"The {} must be non-negative. Instead a value of {} "
"has been "
"provided",
quantity, value
)
);
}
}
double m_polytropic_index;
double m_polytropic_constant;
double m_enthalpy_scale;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,128 @@
module;
#include <memory>
#include <type_traits>
export module mean_field:eos.pressure_surface;
export import :eos.evaluation;
export namespace mean_field::eos {
namespace detail {
template <
ThermodynamicQuantityType InputQuantity,
typename SurfaceState>
[[nodiscard]] constexpr auto pressureSurfaceRelationInput(
const dimensions::PressureValue targetPressure,
const SurfaceState &state
) {
if constexpr (std::same_as<InputQuantity, dimensions::quantity::Pressure>) {
return targetPressure;
} else {
return state.value(InputQuantity{});
}
}
template <typename RelationType> struct PressureSurfaceRelationOperations;
template <typename CarrierQuantity, typename... InputQuantities>
struct PressureSurfaceRelationOperations<Relation<CarrierQuantity, InputQuantities...>> {
template <
typename EquationOfState,
typename SurfaceState>
[[nodiscard]] static dimensions::QuantityValue<CarrierQuantity> requiredCarrierValue(
const EquationOfState &equationOfState,
const dimensions::PressureValue targetPressure,
const SurfaceState &state
) {
return evaluate<CarrierQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
}
template <
typename InputQuantity,
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double inputJacobianContribution(
const EquationOfState &equationOfState,
const dimensions::PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
if constexpr (std::same_as<InputQuantity, dimensions::quantity::Pressure>) {
return 0.0;
} else {
const auto derivative = partialDerivative<CarrierQuantity, InputQuantity>(
equationOfState, pressureSurfaceRelationInput<InputQuantities>(targetPressure, state)...
);
return derivative.value() * variation.value(InputQuantity{}).value();
}
}
template <
typename EquationOfState,
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] static double carrierCorrectionJacobianAction(
const EquationOfState &equationOfState,
const dimensions::PressureValue targetPressure,
const SurfaceState &state,
const SurfaceVariation &variation
) {
return (
0.0 + ... +
inputJacobianContribution<InputQuantities>(equationOfState, targetPressure, state, variation)
);
}
};
} // namespace detail
/*
* EOS-owned resolution of a constant-pressure condition into the carrier
* quantity used by an equation formulation. No field or solver concepts
* enter this type.
*/
template <EquationOfStateModel EquationOfState, ThermodynamicRelationType SelectedRelation>
class ResolvedPressureSurfaceRelation final {
public:
using RelationType = SelectedRelation;
using CarrierQuantity = RelationOutputT<RelationType>;
ResolvedPressureSurfaceRelation(
const EquationOfState &equationOfState,
const dimensions::PressureValue targetPressure
) noexcept
: m_equationOfState(std::addressof(equationOfState)),
m_targetPressure(targetPressure) {
}
[[nodiscard]] dimensions::PressureValue targetPressure() const noexcept {
return m_targetPressure;
}
template <typename SurfaceState>
[[nodiscard]] dimensions::QuantityValue<CarrierQuantity> requiredCarrierValue(const SurfaceState &state) const {
return detail::PressureSurfaceRelationOperations<RelationType>::requiredCarrierValue(
*m_equationOfState, m_targetPressure, state
);
}
template <
typename SurfaceState,
typename SurfaceVariation>
[[nodiscard]] double carrierCorrectionJacobianAction(
const SurfaceState &state,
const SurfaceVariation &variation
) const {
return detail::PressureSurfaceRelationOperations<RelationType>::carrierCorrectionJacobianAction(
*m_equationOfState, m_targetPressure, state, variation
);
}
private:
const EquationOfState *m_equationOfState;
dimensions::PressureValue m_targetPressure;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,145 @@
module;
#include <compare>
#include <concepts>
export module mean_field:eos.quantities;
export import :dimensions.quantities;
export namespace mean_field::eos {
// Compatibility names for the thermodynamic subset now owned by the
// general dimensions partition.
using ThermodynamicQuantity = dimensions::ThermodynamicQuantity;
template <typename Candidate>
concept ThermodynamicQuantityType = dimensions::ThermodynamicQuantityType<Candidate>;
namespace quantity {
using Density = dimensions::quantity::Density;
using Pressure = dimensions::quantity::Pressure;
using SpecificEnthalpy = dimensions::quantity::SpecificEnthalpy;
} // namespace quantity
template <typename T>
concept Numeric = dimensions::Numeric<T>;
template <ThermodynamicQuantityType Quantity> using QuantityValue = dimensions::QuantityValue<Quantity>;
using DensityValue = dimensions::DensityValue;
using PressureValue = dimensions::PressureValue;
using SpecificEnthalpyValue = dimensions::SpecificEnthalpyValue;
template <typename Candidate> using IsQuantityValue = dimensions::IsQuantityValue<Candidate>;
template <typename Candidate>
concept QuantityValueType =
dimensions::QuantityValueType<Candidate> && ThermodynamicQuantityType<dimensions::QuantityOfT<Candidate>>;
template <typename Candidate> using QuantityOf = dimensions::QuantityOf<Candidate>;
template <QuantityValueType Value> using QuantityOfT = dimensions::QuantityOfT<Value>;
template <ThermodynamicQuantityType OutputQuantity, ThermodynamicQuantityType InputQuantity>
class PartialDerivative final {
public:
explicit constexpr PartialDerivative(const double value) noexcept : m_value(value) {
}
[[nodiscard]] constexpr double value() const noexcept {
return m_value;
}
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator+(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator-(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept;
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
Scalar
) noexcept;
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator*(
Scalar,
const PartialDerivative<
OutputQuantity,
InputQuantity> &
) noexcept;
template <Numeric Scalar>
friend constexpr PartialDerivative<
OutputQuantity,
InputQuantity>
operator/(
const PartialDerivative<
OutputQuantity,
InputQuantity> &,
Scalar
) noexcept;
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
Scalar rhs
) noexcept {
return lhs.m_value <=> static_cast<double>(rhs);
}
template <Numeric Scalar>
friend constexpr std::partial_ordering operator<=>(
Scalar lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return static_cast<double>(lhs) <=> rhs.m_value;
}
friend constexpr std::partial_ordering operator<=>(
const PartialDerivative<
OutputQuantity,
InputQuantity> &lhs,
const PartialDerivative<
OutputQuantity,
InputQuantity> &rhs
) noexcept {
return lhs.m_value <=> rhs.m_value;
}
private:
double m_value;
};
template <ThermodynamicQuantityType Quantity> struct WithRespectTo final { };
} // namespace mean_field::eos

View File

@@ -0,0 +1,95 @@
module;
#include <concepts>
#include <cstddef>
#include <tuple>
#include <type_traits>
export module mean_field:eos.relations;
export import :eos.quantities;
export namespace mean_field::eos {
template <typename... Quantities> struct QuantityList final { };
template <typename Output, typename... Inputs> struct Relation final {
using OutputQuantity = Output;
using InputQuantities = QuantityList<Inputs...>;
static constexpr std::size_t inputCount = sizeof...(Inputs);
};
template <typename... Relations> struct RelationCatalog final {
static constexpr std::size_t size = sizeof...(Relations);
};
namespace detail {
template <typename... Types> struct TypesAreUnique;
template <typename Candidate> struct IsThermodynamicRelation : std::false_type { };
template <typename Output, typename... Inputs>
struct IsThermodynamicRelation<Relation<Output, Inputs...>>
: std::bool_constant<
ThermodynamicQuantityType<Output> && (ThermodynamicQuantityType<Inputs> && ...) &&
TypesAreUnique<Inputs...>::value> { };
template <typename... Types> struct TypesAreUnique : std::true_type { };
template <typename First, typename... Remaining>
struct TypesAreUnique<First, Remaining...>
: std::bool_constant<(!std::same_as<First, Remaining> && ...) && TypesAreUnique<Remaining...>::value> { };
template <typename Candidate> struct IsValidRelationCatalog : std::false_type { };
template <typename... Relations>
struct IsValidRelationCatalog<RelationCatalog<Relations...>>
: std::bool_constant<
(sizeof...(Relations) > 0) && (IsThermodynamicRelation<Relations>::value && ...) &&
TypesAreUnique<Relations...>::value> { };
template <typename Catalog, typename RelationType> struct CatalogContainsRelation : std::false_type { };
template <typename... Relations, typename RelationType>
struct CatalogContainsRelation<RelationCatalog<Relations...>, RelationType>
: std::bool_constant<(std::same_as<RelationType, Relations> || ...)> { };
template <typename RelationType, typename Quantity> struct RelationContainsInput : std::false_type { };
template <typename Output, typename... Inputs, typename Quantity>
struct RelationContainsInput<Relation<Output, Inputs...>, Quantity>
: std::bool_constant<(std::same_as<Quantity, Inputs> || ...)> { };
template <std::size_t Index, typename Quantities> struct QuantityAt;
template <std::size_t Index, typename... Quantities> struct QuantityAt<Index, QuantityList<Quantities...>> {
using Type = std::tuple_element_t<Index, std::tuple<Quantities...>>;
};
} // namespace detail
template <typename Candidate>
concept ThermodynamicRelationType = detail::IsThermodynamicRelation<std::remove_cv_t<Candidate>>::value;
template <typename Candidate>
concept ValidRelationCatalog = detail::IsValidRelationCatalog<std::remove_cv_t<Candidate>>::value;
template <typename Catalog, typename RelationType>
inline constexpr bool relationCatalogContains =
detail::CatalogContainsRelation<std::remove_cv_t<Catalog>, std::remove_cv_t<RelationType>>::value;
template <typename RelationType, typename Quantity>
inline constexpr bool relationContainsInput =
detail::RelationContainsInput<std::remove_cv_t<RelationType>, std::remove_cv_t<Quantity>>::value;
template <ThermodynamicRelationType RelationType> using RelationOutputT = typename RelationType::OutputQuantity;
template <std::size_t Index, ThermodynamicRelationType RelationType>
using RelationInputT = typename detail::QuantityAt<Index, typename RelationType::InputQuantities>::Type;
using PressureFromDensity = Relation<dimensions::quantity::Pressure, dimensions::quantity::Density>;
using PressureFromSpecificEnthalpy =
Relation<dimensions::quantity::Pressure, dimensions::quantity::SpecificEnthalpy>;
using SpecificEnthalpyFromDensity = Relation<dimensions::quantity::SpecificEnthalpy, dimensions::quantity::Density>;
using SpecificEnthalpyFromPressure =
Relation<dimensions::quantity::SpecificEnthalpy, dimensions::quantity::Pressure>;
using DensityFromSpecificEnthalpy = Relation<dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy>;
} // namespace mean_field::eos

View File

@@ -0,0 +1,645 @@
module;
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
export module mean_field:eos.runtime;
export import :eos.evaluation;
export namespace mean_field::eos {
class ThermodynamicQuantityId final {
public:
explicit constexpr ThermodynamicQuantityId(const std::string_view name) noexcept : m_name(name) {
}
[[nodiscard]] constexpr std::string_view name() const noexcept {
return m_name;
}
[[nodiscard]] friend constexpr bool operator==(
const ThermodynamicQuantityId &,
const ThermodynamicQuantityId &
) noexcept = default;
private:
std::string_view m_name;
};
template <typename Quantity>
concept RuntimeIdentifiedThermodynamicQuantity = ThermodynamicQuantityType<Quantity> && requires {
{ Quantity::identifier } -> std::convertible_to<std::string_view>;
} && (std::string_view{Quantity::identifier}.size() > 0);
template <RuntimeIdentifiedThermodynamicQuantity Quantity>
inline constexpr ThermodynamicQuantityId thermodynamicQuantityId{std::string_view{Quantity::identifier}};
struct RuntimeQuantityValue final {
ThermodynamicQuantityId quantity;
double value;
};
struct RuntimeRelationDescriptor final {
ThermodynamicQuantityId outputQuantity;
std::span<const ThermodynamicQuantityId> inputQuantities;
std::uint64_t partialDerivativeMask;
[[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept {
return inputIndex < inputQuantities.size() &&
(partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0;
}
};
namespace detail {
template <typename RelationType> struct HasRuntimeQuantityIdentifiers : std::false_type { };
template <typename Output, typename... Inputs>
struct HasRuntimeQuantityIdentifiers<Relation<Output, Inputs...>>
: std::bool_constant<
RuntimeIdentifiedThermodynamicQuantity<Output> &&
(RuntimeIdentifiedThermodynamicQuantity<Inputs> && ...)> { };
template <typename RelationType> struct RuntimeRelationQuantities;
template <typename Output, typename... Inputs> struct RuntimeRelationQuantities<Relation<Output, Inputs...>> {
using Type = std::tuple<Output, Inputs...>;
};
template <typename... Relations>
using RuntimeCatalogQuantityTuple =
decltype(std::tuple_cat(std::declval<typename RuntimeRelationQuantities<Relations>::Type>()...));
template <
typename FirstQuantity,
typename SecondQuantity>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreCompatible() {
if constexpr (std::same_as<FirstQuantity, SecondQuantity>) {
return true;
} else {
return thermodynamicQuantityId<FirstQuantity> != thermodynamicQuantityId<SecondQuantity>;
}
}
template <
typename QuantityTuple,
std::size_t First,
std::size_t... Offsets>
[[nodiscard]] consteval bool runtimeQuantityIdentifierIsUnambiguous(std::index_sequence<Offsets...>) {
return (
runtimeQuantityIdentifiersAreCompatible<
std::tuple_element_t<First, QuantityTuple>,
std::tuple_element_t<First + 1 + Offsets, QuantityTuple>>() &&
...
);
}
template <
typename QuantityTuple,
std::size_t... Indices>
[[nodiscard]] consteval bool runtimeQuantityIdentifiersAreUnambiguous(std::index_sequence<Indices...>) {
return (
runtimeQuantityIdentifierIsUnambiguous<QuantityTuple, Indices>(
std::make_index_sequence<std::tuple_size_v<QuantityTuple> - Indices - 1>{}
) &&
...
);
}
template <bool QuantitiesAreIdentified, typename... Relations>
struct RuntimeRelationsAreSupported : std::false_type { };
template <typename... Relations>
struct RuntimeRelationsAreSupported<true, Relations...>
: std::bool_constant<runtimeQuantityIdentifiersAreUnambiguous<RuntimeCatalogQuantityTuple<Relations...>>(
std::make_index_sequence<std::tuple_size_v<RuntimeCatalogQuantityTuple<Relations...>>>{}
)> { };
template <typename Catalog> struct RuntimeCatalogIsSupported : std::false_type { };
template <typename... Relations>
struct RuntimeCatalogIsSupported<RelationCatalog<Relations...>>
: RuntimeRelationsAreSupported<(HasRuntimeQuantityIdentifiers<Relations>::value && ...), Relations...> { };
} // namespace detail
template <typename Candidate>
concept RuntimeEquationOfStateModel =
EquationOfStateModel<Candidate> &&
detail::RuntimeCatalogIsSupported<typename std::remove_cvref_t<Candidate>::Relations>::value;
namespace detail {
template <typename EquationOfState, typename RelationType> struct RuntimeRelationStorage;
template <typename EquationOfState, typename Output, typename... Inputs>
struct RuntimeRelationStorage<EquationOfState, Relation<Output, Inputs...>> {
using RelationType = Relation<Output, Inputs...>;
static_assert(
sizeof...(Inputs) <= 64,
"Runtime EOS relation descriptors support at most 64 inputs."
);
inline static constexpr std::array<ThermodynamicQuantityId, sizeof...(Inputs)> inputQuantityIds{
thermodynamicQuantityId<Inputs>...
};
template <std::size_t... Indices>
[[nodiscard]] static consteval std::uint64_t makePartialDerivativeMask(std::index_sequence<Indices...>) {
using InputTuple = std::tuple<Inputs...>;
return (
std::uint64_t{0} | ... |
(SupportsPartialDerivative<EquationOfState, RelationType, std::tuple_element_t<Indices, InputTuple>>
? (std::uint64_t{1} << Indices)
: std::uint64_t{0})
);
}
inline static constexpr std::uint64_t partialDerivativeMask =
makePartialDerivativeMask(std::index_sequence_for<Inputs...>{});
inline static constexpr RuntimeRelationDescriptor descriptor{
thermodynamicQuantityId<Output>, std::span<const ThermodynamicQuantityId>{inputQuantityIds},
partialDerivativeMask
};
};
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogStorage;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogStorage<EquationOfState, RelationCatalog<Relations...>> {
inline static constexpr std::array descriptors{
RuntimeRelationStorage<EquationOfState, Relations>::descriptor...
};
};
[[nodiscard]] inline std::expected<
double,
EvaluationError>
runtimeEvaluationFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimeRelation(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...>,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::evaluate<Output>(equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...)
.value();
};
try {
return invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
return std::unexpected<EvaluationError>{error};
}
}
template <
typename InputQuantity,
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] bool tryRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues,
std::expected<
double,
EvaluationError> &result
) {
if (withRespectTo != thermodynamicQuantityId<InputQuantity>) {
return false;
}
if constexpr (SupportsPartialDerivative<EquationOfState, Relation<Output, Inputs...>, InputQuantity>) {
const auto invoke = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
return eos::partialDerivative<Output, InputQuantity>(
equationOfState, QuantityValue<Inputs>{inputValues[Indices].value}...
)
.value();
};
try {
result = invoke(std::index_sequence_for<Inputs...>{});
} catch (const EvaluationError &error) {
result = std::unexpected<EvaluationError>{error};
}
} else {
result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return true;
}
template <
typename EquationOfState,
typename Output,
typename... Inputs>
[[nodiscard]] std::expected<
double,
EvaluationError>
evaluateRuntimePartialDerivative(
const EquationOfState &equationOfState,
Relation<
Output,
Inputs...> relation,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_derivative,
"The requested quantity is not an input to the EOS relation."
);
const bool matched =
(tryRuntimePartialDerivative<Inputs>(equationOfState, relation, withRespectTo, inputValues, result) ||
...);
static_cast<void>(matched);
return result;
}
template <
typename EquationOfState,
typename RelationType>
[[nodiscard]] bool runtimeRelationMatches(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const RuntimeRelationDescriptor &descriptor =
RuntimeRelationStorage<EquationOfState, RelationType>::descriptor;
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputValues.size()) {
return false;
}
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
return false;
}
}
return true;
}
template <typename EquationOfState, typename Catalog> struct RuntimeCatalogDispatch;
template <typename EquationOfState, typename... Relations>
struct RuntimeCatalogDispatch<EquationOfState, RelationCatalog<Relations...>> {
[[nodiscard]] static std::expected<
double,
EvaluationError>
evaluate(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimeRelation(equationOfState, Relations{}, inputValues), true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
[[nodiscard]] static std::expected<
double,
EvaluationError>
partialDerivative(
const void *object,
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) {
const auto &equationOfState = *static_cast<const EquationOfState *>(object);
std::expected<double, EvaluationError> result = runtimeEvaluationFailure(
EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available."
);
const bool matched =
((runtimeRelationMatches<EquationOfState, Relations>(outputQuantity, inputValues)
? (result = evaluateRuntimePartialDerivative(
equationOfState, Relations{}, withRespectTo, inputValues
),
true)
: false) ||
...);
static_cast<void>(matched);
return result;
}
};
template <RuntimeEquationOfStateModel EquationOfState>
using RuntimeAdapter = RuntimeCatalogDispatch<EquationOfState, typename EquationOfState::Relations>;
template <RuntimeEquationOfStateModel EquationOfState>
[[nodiscard]] constexpr std::span<const RuntimeRelationDescriptor> runtimeRelationDescriptors() noexcept {
return RuntimeCatalogStorage<EquationOfState, typename EquationOfState::Relations>::descriptors;
}
} // namespace detail
class EquationOfStateView final {
public:
template <RuntimeEquationOfStateModel EquationOfState>
explicit EquationOfStateView(EquationOfState &equationOfState) noexcept
: m_object(std::addressof(equationOfState)),
m_relations(detail::runtimeRelationDescriptors<std::remove_cv_t<EquationOfState>>()),
m_evaluate(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::evaluate),
m_partialDerivative(&detail::RuntimeAdapter<std::remove_cv_t<EquationOfState>>::partialDerivative) {
}
[[nodiscard]] std::span<const RuntimeRelationDescriptor> relations() const noexcept {
return m_relations;
}
[[nodiscard]] bool supports(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
return findRelation(outputQuantity, inputQuantities) != nullptr;
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity... InputQuantities>
[[nodiscard]] bool supports() const noexcept {
constexpr std::array<ThermodynamicQuantityId, sizeof...(InputQuantities)> inputs{
thermodynamicQuantityId<InputQuantities>...
};
return supports(thermodynamicQuantityId<OutputQuantity>, std::span<const ThermodynamicQuantityId>{inputs});
}
[[nodiscard]] std::expected<
RuntimeQuantityValue,
EvaluationError>
tryEvaluate(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
auto result = m_evaluate(m_object, outputQuantity, inputValues);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return RuntimeQuantityValue{outputQuantity, *result};
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
QuantityValue<OutputQuantity>,
EvaluationError>
tryEvaluate(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryEvaluate(
thermodynamicQuantityId<OutputQuantity>, std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return QuantityValue<OutputQuantity>{result->value};
}
[[nodiscard]] std::expected<
double,
EvaluationError>
tryPartialDerivative(
const ThermodynamicQuantityId outputQuantity,
const ThermodynamicQuantityId withRespectTo,
const std::span<const RuntimeQuantityValue> inputValues
) const {
const auto validation = validateRelationRequest(outputQuantity, inputValues);
if (!validation.has_value()) {
return std::unexpected<EvaluationError>{validation.error()};
}
const RuntimeRelationDescriptor &descriptor = **validation;
bool derivativeAvailable = false;
for (std::size_t index = 0; index < descriptor.inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] == withRespectTo) {
derivativeAvailable = descriptor.hasPartialDerivative(index);
break;
}
}
if (!derivativeAvailable) {
return runtimeFailure<double>(
EvaluationErrorCode::unsupported_derivative,
"The requested EOS partial derivative is not available."
);
}
return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues);
}
template <
RuntimeIdentifiedThermodynamicQuantity OutputQuantity,
RuntimeIdentifiedThermodynamicQuantity InputQuantity,
QuantityValueType... InputValues>
[[nodiscard]] std::expected<
PartialDerivative<
OutputQuantity,
InputQuantity>,
EvaluationError>
tryPartialDerivative(const InputValues... inputValues) const {
constexpr bool inputsHaveRuntimeIdentifiers =
(RuntimeIdentifiedThermodynamicQuantity<QuantityOfT<InputValues>> && ...);
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
const std::array<RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
RuntimeQuantityValue{thermodynamicQuantityId<QuantityOfT<InputValues>>, inputValues.value()}...
};
auto result = tryPartialDerivative(
thermodynamicQuantityId<OutputQuantity>, thermodynamicQuantityId<InputQuantity>,
std::span<const RuntimeQuantityValue>{runtimeInputs}
);
if (!result.has_value()) {
return std::unexpected<EvaluationError>{result.error()};
}
return PartialDerivative<OutputQuantity, InputQuantity>{*result};
}
private:
using RuntimeEvaluateFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
using RuntimePartialDerivativeFunction = std::expected<
double,
EvaluationError> (*)(
const void *,
ThermodynamicQuantityId,
ThermodynamicQuantityId,
std::span<const RuntimeQuantityValue>
);
[[nodiscard]] const RuntimeRelationDescriptor *findRelation(
const ThermodynamicQuantityId outputQuantity,
const std::span<const ThermodynamicQuantityId> inputQuantities
) const noexcept {
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity ||
descriptor.inputQuantities.size() != inputQuantities.size()) {
continue;
}
bool matches = true;
for (std::size_t index = 0; index < inputQuantities.size(); ++index) {
if (descriptor.inputQuantities[index] != inputQuantities[index]) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
return nullptr;
}
[[nodiscard]] std::expected<
const RuntimeRelationDescriptor *,
EvaluationError>
validateRelationRequest(
const ThermodynamicQuantityId outputQuantity,
const std::span<const RuntimeQuantityValue> inputValues
) const {
bool outputAvailable = false;
bool inputCountAvailable = false;
for (const RuntimeRelationDescriptor &descriptor : m_relations) {
if (descriptor.outputQuantity != outputQuantity) {
continue;
}
outputAvailable = true;
if (descriptor.inputQuantities.size() != inputValues.size()) {
continue;
}
inputCountAvailable = true;
bool matches = true;
for (std::size_t index = 0; index < inputValues.size(); ++index) {
if (descriptor.inputQuantities[index] != inputValues[index].quantity) {
matches = false;
break;
}
}
if (matches) {
return std::addressof(descriptor);
}
}
if (!outputAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::unsupported_relation,
"The EOS does not provide a relation for output quantity '" + std::string{outputQuantity.name()} +
"'."
);
}
if (!inputCountAvailable) {
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_count, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied number of inputs."
);
}
return runtimeFailure<const RuntimeRelationDescriptor *>(
EvaluationErrorCode::wrong_input_quantity, "No EOS relation for output quantity '" +
std::string{outputQuantity.name()} +
"' accepts the supplied input quantities."
);
}
template <typename Value>
[[nodiscard]] static std::expected<
Value,
EvaluationError>
runtimeFailure(
const EvaluationErrorCode code,
std::string message
) {
return std::unexpected<EvaluationError>{EvaluationError{code, std::move(message)}};
}
const void *m_object;
std::span<const RuntimeRelationDescriptor> m_relations;
RuntimeEvaluateFunction m_evaluate;
RuntimePartialDerivativeFunction m_partialDerivative;
};
} // namespace mean_field::eos

View File

@@ -0,0 +1,183 @@
module;
#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
export module mean_field:equilibrium.stellar_discretization;
export import :fem;
export import :mapping.domain_mapper;
export import :normalization.physical_riesz;
export namespace mean_field::equilibrium {
/*
* An explicit, non-owning view of the numerical discretization used by a
* stellar equilibrium problem. The referenced FEM and mapper must outlive
* every problem and structure that uses this view.
*
* Ownership cannot move here yet because FEM currently also contains
* mutable field workspaces. Separating those workspaces is a prerequisite
* for shared discretization ownership by solved Structure objects.
*/
template <normalization::NormalizationPrescription Normalization>
class StellarDiscretizationFor final {
public:
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
explicit StellarDiscretizationFor(fem::FEM &finiteElementModel)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel),
normalization::Unnormalized{}
) {
}
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper
)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
domainMapper,
normalization::Unnormalized{}
) {
}
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
NormalizationPrescriptionType normalizationPrescription
)
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel),
std::move(normalizationPrescription)
) {
}
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
NormalizationPrescriptionType normalizationPrescription
)
: m_finiteElementModel(std::addressof(finiteElementModel)),
m_domainMapper(std::addressof(domainMapper)),
m_normalizationPrescription(std::move(normalizationPrescription)) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
}
}
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
return *m_finiteElementModel;
}
[[nodiscard]] const mapping::DomainMapper &domainMapper() const noexcept {
return *m_domainMapper;
}
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const noexcept {
return m_normalizationPrescription;
}
[[nodiscard]] bool isCurrent() const noexcept {
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
}
private:
[[nodiscard]] static const mapping::DomainMapper &RequireDomainMapper(const fem::FEM &finiteElementModel) {
if (finiteElementModel.domainMapperStateless == nullptr) {
throw std::invalid_argument("A stellar discretization requires a domain mapper.");
}
return *finiteElementModel.domainMapperStateless;
}
fem::FEM *m_finiteElementModel;
const mapping::DomainMapper *m_domainMapper;
NormalizationPrescriptionType m_normalizationPrescription;
};
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
template <typename Candidate> struct IsStellarDiscretization : std::false_type { };
template <normalization::NormalizationPrescription Normalization>
struct IsStellarDiscretization<StellarDiscretizationFor<Normalization>> : std::true_type { };
template <typename Candidate>
concept StellarDiscretizationType = IsStellarDiscretization<std::remove_cvref_t<Candidate>>::value;
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
domainMapper,
std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
fem::FEM &,
mapping::DomainMapper &&,
Normalization
) = delete;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
fem::FEM &,
const mapping::DomainMapper &&,
Normalization
) = delete;
} // namespace mean_field::equilibrium

View File

@@ -8,7 +8,6 @@ module;
export module mean_field:fem;
export import :physics.contexts;
export import :boundary.contexts;
export import :mapping.domain_mapper;
export import :utils.misc;
@@ -29,6 +28,7 @@ export namespace mean_field::fem {
stroid::StroidMesh smesh;
std::unique_ptr<mfem::ParMesh> mesh;
std::unique_ptr<mfem::ParMesh> logicalReferenceMesh;
// =====================================================================
// Compile-time field descriptors
@@ -64,6 +64,14 @@ export namespace mean_field::fem {
std::unique_ptr<mfem::ParGridFunction> displacement;
/*
* Scalar companion of the vector displacement space. Only its
* StellarSurface true DOFs become surface-deformation coordinates.
* Sharing displacementFec guarantees identical scalar basis
* functions without duplicating the finite-element collection.
*/
std::unique_ptr<mfem::ParFiniteElementSpace> surfaceDeformationFes;
// =====================================================================
// Density field
// =====================================================================
@@ -92,38 +100,9 @@ export namespace mean_field::fem {
// =====================================================================
// Domain mapping
//
// These are declared after displacement so that they are destroyed
// before the displacement grid function to which mapping may refer.
// DomainMapper is retained only for legacy integrators. New operators
// use DomainMapperStateless exclusively.
// =====================================================================
std::unique_ptr<mapping::DomainMapper> mapping;
std::unique_ptr<mapping::DomainMapperStateless> domainMapperStateless;
// =====================================================================
// Block layouts
//
// These arrays are retained only for legacy code. Canonical operator
// layouts are defined by the compile-time forms in :utils.blocks.
//
// Main system: [Displacement | Density]
// Gravity system: [Flux | Potential]
// =====================================================================
mfem::Array<int> blockTrueOffsets;
mfem::Array<int> gravityBlockTrueOffsets;
// =====================================================================
// Boundary conditions and domain masks
// =====================================================================
mfem::Array<int> essentialDisplacementTdofs;
mfem::Array<int> vacuumDensityTdofs;
mfem::Array<int> vacuumEnthalpyTdofs;
mfem::Array<int> vacuumDisplacementTdofs;
std::unique_ptr<mapping::DomainMapper> domainMapperStateless;
// =====================================================================
// Global diagnostics
@@ -133,10 +112,9 @@ export namespace mean_field::fem {
mfem::DenseMatrix Q;
// =====================================================================
// Physics and boundary contexts
// Boundary context
// =====================================================================
physics::GravityContext gravityContext;
boundary::BoundaryContext boundaryContext;
std::unique_ptr<quadrature::RuleFactory> quadratureFactory;
@@ -146,32 +124,26 @@ export namespace mean_field::fem {
// =====================================================================
[[nodiscard]] bool okay() const {
return mesh != nullptr &&
return mesh != nullptr && logicalReferenceMesh != 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 &&
surfaceDeformationFes != 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 &&
blockTrueOffsets.Size() == 3 &&
gravityBlockTrueOffsets.Size() == 3;
domainMapperStateless != nullptr && quadratureFactory != nullptr;
}
[[nodiscard]] bool has_mapping() const {
return mapping != nullptr;
return domainMapperStateless != nullptr && displacement != nullptr && compactificationCoordinate != nullptr;
}
};

View File

@@ -6,6 +6,7 @@ module;
#include <type_traits>
export module mean_field:field.base;
export import :utils.domain;
export namespace mean_field::field {
template <typename... Ts> struct TypeList { };
@@ -13,11 +14,9 @@ export namespace mean_field::field {
template <typename T, typename ListT> struct TypeListContains;
template <typename T, typename... Ts>
struct TypeListContains<T, TypeList<Ts...>>
: std::bool_constant<(std::same_as<T, Ts> || ...)> { };
struct TypeListContains<T, TypeList<Ts...>> : std::bool_constant<(std::same_as<T, Ts> || ...)> { };
template <typename T, typename ListT>
inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
template <typename T, typename ListT> inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
enum class StorageKind { finite_element, global_scalar };
@@ -44,14 +43,13 @@ export namespace mean_field::field {
};
template <typename SpaceT>
concept SpaceTag = std::same_as<SpaceT, L2> || std::same_as<SpaceT, H1> ||
std::same_as<SpaceT, RT> || std::same_as<SpaceT, ND>;
concept SpaceTag =
std::same_as<SpaceT, L2> || std::same_as<SpaceT, H1> || std::same_as<SpaceT, RT> || std::same_as<SpaceT, ND>;
template <SpaceTag SpaceT, int RankV>
inline constexpr bool spaceSupportsRank =
(std::same_as<SpaceT, H1> && (RankV == 0 || RankV == 1)) ||
(std::same_as<SpaceT, L2> && (RankV == 0 || RankV == 1)) ||
(std::same_as<SpaceT, RT> && RankV == 1) ||
(std::same_as<SpaceT, L2> && (RankV == 0 || RankV == 1)) || (std::same_as<SpaceT, RT> && RankV == 1) ||
(std::same_as<SpaceT, ND> && RankV == 1);
// -------------------------------------------------------------------------
@@ -105,51 +103,39 @@ export namespace mean_field::field {
template <typename T> struct IsCurl : std::false_type { };
template <typename SourceT>
struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_type { };
template <typename SourceT> struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_type { };
template <typename SourceT>
struct IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type {
};
template <typename SourceT> struct IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type { };
template <typename SourceT>
struct IsCurl<FieldRelation::Curl<SourceT>> : std::true_type { };
template <typename SourceT> struct IsCurl<FieldRelation::Curl<SourceT>> : std::true_type { };
template <typename RelationT>
concept ValidRelation =
std::same_as<RelationT, FieldRelation::Independent> ||
IsGradient<RelationT>::value || IsDivergence<RelationT>::value ||
IsCurl<RelationT>::value;
concept ValidRelation = std::same_as<RelationT, FieldRelation::Independent> || IsGradient<RelationT>::value ||
IsDivergence<RelationT>::value || IsCurl<RelationT>::value;
template <typename RelationT> struct RelationTarget {
using Type = void;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Gradient<SourceT>> {
template <typename SourceT> struct RelationTarget<FieldRelation::Gradient<SourceT>> {
using Type = SourceT;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Divergence<SourceT>> {
template <typename SourceT> struct RelationTarget<FieldRelation::Divergence<SourceT>> {
using Type = SourceT;
};
template <typename SourceT>
struct RelationTarget<FieldRelation::Curl<SourceT>> {
template <typename SourceT> struct RelationTarget<FieldRelation::Curl<SourceT>> {
using Type = SourceT;
};
template <typename QuantityT>
using RelationTargetT =
typename RelationTarget<typename QuantityT::Relation>::Type;
template <typename QuantityT> using RelationTargetT = typename RelationTarget<typename QuantityT::Relation>::Type;
// -------------------------------------------------------------------------
// Field quantities
// -------------------------------------------------------------------------
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT>
struct Quantity {
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT> struct Quantity {
using Relation = RelationT;
using Discretization = DiscT;
using Space = typename DiscT::Space;
@@ -173,11 +159,9 @@ export namespace mean_field::field {
);
};
template <ValidRelation RelationT, DiscretizationTag DiscT>
using ScalarQ = Quantity<0, RelationT, DiscT>;
template <ValidRelation RelationT, DiscretizationTag DiscT> using ScalarQ = Quantity<0, RelationT, DiscT>;
template <ValidRelation RelationT, DiscretizationTag DiscT>
using VectorQ = Quantity<1, RelationT, DiscT>;
template <ValidRelation RelationT, DiscretizationTag DiscT> using VectorQ = Quantity<1, RelationT, DiscT>;
struct GlobalScalarQ {
using Relation = FieldRelation::Independent;
@@ -188,8 +172,7 @@ export namespace mean_field::field {
};
template <typename T>
concept FieldQuantity =
requires {
concept FieldQuantity = requires {
typename T::Relation;
typename T::Discretization;
typename T::Space;
@@ -198,63 +181,48 @@ export namespace mean_field::field {
{ T::familyOrder } -> std::convertible_to<int>;
{ T::storageKind } -> std::convertible_to<StorageKind>;
{ T::staticBlockSize } -> std::convertible_to<int>;
} && SpaceTag<typename T::Space> &&
T::storageKind == StorageKind::finite_element;
} && SpaceTag<typename T::Space> && T::storageKind == StorageKind::finite_element;
template <typename T>
concept GlobalScalarQuantity =
requires {
concept GlobalScalarQuantity = requires {
typename T::Relation;
{ T::rankValue } -> std::convertible_to<int>;
{ T::storageKind } -> std::convertible_to<StorageKind>;
{ T::staticBlockSize } -> std::convertible_to<int>;
} && T::rankValue == 0 &&
T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1;
} && T::rankValue == 0 && T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1;
template <typename T>
concept RegisteredQuantity = FieldQuantity<T> || GlobalScalarQuantity<T>;
template <typename QuantityT>
concept DerivedQuantity = FieldQuantity<QuantityT> &&
(!std::same_as<RelationTargetT<QuantityT>, void>);
concept DerivedQuantity = FieldQuantity<QuantityT> && (!std::same_as<RelationTargetT<QuantityT>, void>);
// -------------------------------------------------------------------------
// Compile-time discretization constraints
// -------------------------------------------------------------------------
template <FieldQuantity FluxT, FieldQuantity PotentialT>
struct RtL2StablePair {
template <FieldQuantity FluxT, FieldQuantity PotentialT> struct RtL2StablePair {
static consteval void validate() {
static_assert(
std::same_as<typename FluxT::Space, RT>,
"The flux in an RT/L2 pair must use Raviart-Thomas elements."
std::same_as<typename FluxT::Space, RT>, "The flux in an RT/L2 pair must use Raviart-Thomas elements."
);
static_assert(
std::same_as<typename PotentialT::Space, L2>,
"The potential in an RT/L2 pair must use L2 elements."
std::same_as<typename PotentialT::Space, L2>, "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 <typename... ConstraintTs>
consteval bool validate_constraints(TypeList<ConstraintTs...>) {
template <typename... ConstraintTs> consteval bool validate_constraints(TypeList<ConstraintTs...>) {
(ConstraintTs::validate(), ...);
return true;
}
@@ -276,16 +244,11 @@ export namespace mean_field::field {
template <typename OperationT>
concept FieldOperationTag =
std::same_as<OperationT, FieldOperation::Value> ||
std::same_as<OperationT, FieldOperation::Gradient> ||
std::same_as<OperationT, FieldOperation::Divergence> ||
std::same_as<OperationT, FieldOperation::Curl> ||
std::same_as<OperationT, FieldOperation::Value> || std::same_as<OperationT, FieldOperation::Gradient> ||
std::same_as<OperationT, FieldOperation::Divergence> || std::same_as<OperationT, FieldOperation::Curl> ||
std::same_as<OperationT, FieldOperation::NormalTrace>;
template <
RegisteredQuantity QuantityT,
FieldOperationTag OperationT = FieldOperation::Value>
struct Operand {
template <RegisteredQuantity QuantityT, FieldOperationTag OperationT = FieldOperation::Value> struct Operand {
using Quantity = QuantityT;
using Operation = OperationT;
@@ -298,12 +261,10 @@ export namespace mean_field::field {
};
template <typename T>
concept FieldOperand =
requires {
concept FieldOperand = requires {
typename T::Quantity;
typename T::Operation;
} && RegisteredQuantity<typename T::Quantity> &&
FieldOperationTag<typename T::Operation>;
} && RegisteredQuantity<typename T::Quantity> && FieldOperationTag<typename T::Operation>;
// -------------------------------------------------------------------------
// 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 <auto PolicyKeyV, std::size_t DynamicOrderCountV, FieldOperand... OperandTs> 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<std::size_t>;
};
template <typename ListT>
struct IsRegisteredQuantityList : std::false_type { };
template <typename ListT> struct IsRegisteredQuantityList : std::false_type { };
template <RegisteredQuantity... QuantityTs>
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type {
};
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type { };
template <typename ListT>
inline constexpr bool isRegisteredQuantityList =
IsRegisteredQuantityList<ListT>::value;
template <typename ListT> inline constexpr bool isRegisteredQuantityList = IsRegisteredQuantityList<ListT>::value;
template <typename ListT> struct IsFieldFormList : std::false_type { };
template <FieldForm... FormTs>
struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
template <FieldForm... FormTs> struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
template <typename ListT> inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
struct FieldSupport { };
template <utils::domain::IsDomainOrSet DomainT> struct DomainSupport final : FieldSupport {
using Domain = DomainT;
};
struct NonSpatialSupport final : FieldSupport { };
template <typename T> constexpr bool isDomainSupportV = false;
template <utils::domain::IsDomainOrSet DomainT> constexpr bool isDomainSupportV<DomainSupport<DomainT>> = true;
template <typename T>
concept IsDomainSupport = isDomainSupportV<T>;
template <typename T>
concept IsFieldSupport = std::derived_from<T, FieldSupport>;
template <typename FieldT> using FieldSupportT = typename FieldT::Support;
template <typename FieldT>
concept DomainSupportedField = requires { typename FieldT::Support; } && IsDomainSupport<FieldSupportT<FieldT>>;
template <typename FieldT>
concept NonSpatialField =
requires { typename FieldT::Support; } && std::same_as<FieldSupportT<FieldT>, NonSpatialSupport>;
template <DomainSupportedField FieldT> using FieldDomainT = typename FieldSupportT<FieldT>::Domain;
template <typename ListT>
inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
} // namespace mean_field::field

File diff suppressed because it is too large Load Diff

View File

@@ -3,84 +3,71 @@ module;
#include <concepts>
#include <string_view>
#ifndef MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT
#define MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT 0
#endif
export module mean_field:field.registry;
export import :dimensions.quantities;
export import :field.base;
export import :quadrature.policy;
export import :utils.domain;
export namespace mean_field::field {
inline constexpr int uniformPolynomialOrderIncrement = MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT;
static_assert(uniformPolynomialOrderIncrement >= 0);
// =========================================================================
// Density
// =========================================================================
struct Density {
static constexpr std::string_view name = "density";
static constexpr int scalarOrder = 2;
static constexpr int scalarOrder = 2 + uniformPolynomialOrderIncrement;
struct Scalar final
: ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
using PhysicalQuantity = dimensions::quantity::Density;
using Support = DomainSupport<utils::domain::Stellar>;
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
static constexpr std::string_view symbol = "ρ";
};
using Quantities = TypeList<Scalar>;
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<Scalar>,
Operand<Scalar>>;
using ProjectionMass = FormSpec<quadrature::Term::density_projection, 0, Operand<Scalar>, Operand<Scalar>>;
// Projection RHS with one runtime coefficient order.
using ProjectionSource = FormSpec<
quadrature::Term::density_projection,
1,
Operand<Scalar>>;
using ProjectionSource = FormSpec<quadrature::Term::density_projection, 1, Operand<Scalar>>;
// Density-space contribution to the barotropic EOS closure:
// (rho, q_rho).
using EosClosureMass = FormSpec<
quadrature::Term::eos_closure,
0,
Operand<Scalar>,
Operand<Scalar>>;
using EosClosureMass = FormSpec<quadrature::Term::eos_closure, 0, Operand<Scalar>, Operand<Scalar>>;
// Integral of density over the physical volume.
using MassConservation = FormSpec<
quadrature::Term::mass_conservation,
0,
Operand<Scalar>>;
using MassConservation = FormSpec<quadrature::Term::mass_conservation, 0, Operand<Scalar>>;
// 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<Scalar>>;
using MassNormalization = FormSpec<quadrature::Term::mass_normalization, 0, Operand<Scalar>>;
// Integral of rho * x. The combined position-coefficient order is
// supplied as one dynamic order.
using CenterOfMass =
FormSpec<quadrature::Term::center_of_mass, 1, Operand<Scalar>>;
using CenterOfMass = FormSpec<quadrature::Term::center_of_mass, 1, Operand<Scalar>>;
// Integral of rho times the quadratic position tensor. The
// combined tensor-coefficient order is supplied dynamically.
using Quadrupole =
FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
using Quadrupole = FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
using ErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Scalar>,
Operand<Scalar>>;
using ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Scalar>, Operand<Scalar>>;
};
using FormList = TypeList<
@@ -101,16 +88,16 @@ export namespace mean_field::field {
struct Gravity {
static constexpr std::string_view name = "gravity";
static constexpr int potentialOrder = 2;
static constexpr int fluxOrder = 2;
static constexpr int potentialOrder = 2 + uniformPolynomialOrderIncrement;
static constexpr int fluxOrder = 2 + uniformPolynomialOrderIncrement;
struct Potential final
: ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
using Support = DomainSupport<utils::domain::All>;
struct Potential final : ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
static constexpr std::string_view symbol = "φ";
};
struct Flux final
: VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
struct Flux final : VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
static constexpr std::string_view symbol = "∇φ";
};
@@ -118,17 +105,12 @@ export namespace mean_field::field {
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
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<Flux>,
Operand<Flux>>;
using HDivMass = FormSpec<quadrature::Term::gravity_hdiv_mass, 0, Operand<Flux>, Operand<Flux>>;
using DivergenceCoupling = FormSpec<
quadrature::Term::gravity_divergence,
@@ -144,31 +126,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<Density::Scalar>,
Operand<Potential>>;
using SourceLinear =
FormSpec<quadrature::Term::gravity_source, 0, Operand<Density::Scalar>, Operand<Potential>>;
// Mixed density-to-potential projection. Both trial and test
// orders are registered quantities.
using SourceProjection = FormSpec<
quadrature::Term::gravity_source,
0,
Operand<Density::Scalar>,
Operand<Potential>>;
using SourceProjection =
FormSpec<quadrature::Term::gravity_source, 0, Operand<Density::Scalar>, Operand<Potential>>;
using PotentialErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Potential>,
Operand<Potential>>;
using PotentialErrorNorm =
FormSpec<quadrature::Term::error_norm, 0, Operand<Potential>, Operand<Potential>>;
using FluxErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Flux>,
Operand<Flux>>;
using FluxErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Flux>, Operand<Flux>>;
};
using FormList = TypeList<
@@ -187,18 +156,18 @@ export namespace mean_field::field {
struct Displacement {
static constexpr std::string_view name = "displacement";
static constexpr int vectorOrder = 3;
static constexpr int vectorOrder = 3 + uniformPolynomialOrderIncrement;
struct Vector final
: VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
using Support = DomainSupport<utils::domain::All>;
struct Vector final : VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
static constexpr std::string_view symbol = "d";
};
using Quantities = TypeList<Vector>;
using Constraints = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
@@ -211,19 +180,44 @@ export namespace mean_field::field {
Operand<Vector, FieldOperation::Gradient>,
Operand<Vector, FieldOperation::Gradient>>;
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<Vector>,
Operand<Density::Scalar>,
Operand<Gravity::Flux>,
Operand<Vector, FieldOperation::Gradient>,
Operand<Vector>>;
// 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<quadrature::Term::centrifugal, 1, Operand<Density::Scalar>, Operand<Vector>>;
using ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Vector>, Operand<Vector>>;
};
using FormList = TypeList<Form::MeshExtension, Form::ErrorNorm>;
using FormList = TypeList<Form::MeshExtension, Form::GravityForce, Form::CentrifugalForce, Form::ErrorNorm>;
};
// Current realization of MultiplierFor<FixedTotalMass>. This remains a
// barotrope-specific field representation: the specification compiler,
// rather than the universal state registry, decides when it is present.
struct BarotropicConstant {
static constexpr std::string_view name = "barotropic_constant";
using Support = NonSpatialSupport;
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "C";
};
@@ -232,8 +226,49 @@ export namespace mean_field::field {
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
// Scalar angular speed generated by FixedAngularMomentum. The axis and
// center belong to the compiled invariant, so the nonlinear coordinate
// contains only the signed speed along that fixed unit axis.
struct AngularVelocity {
static constexpr std::string_view name = "angular_velocity";
using PhysicalQuantity = dimensions::quantity::AngularVelocity;
using Support = NonSpatialSupport;
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "Omega";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
// Solver border generated by FixedCentralDensity. This is deliberately a
// non-spatial numerical coordinate rather than a physical stellar field.
struct CentralDensityBorder {
static constexpr std::string_view name = "central_density_border";
using Support = NonSpatialSupport;
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "lambda_rho_c";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
@@ -248,18 +283,19 @@ export namespace mean_field::field {
struct Enthalpy {
static constexpr std::string_view name = "specific_enthalpy";
static constexpr int scalarOrder = 3;
static constexpr int scalarOrder = 3 + uniformPolynomialOrderIncrement;
struct Scalar final
: ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
using PhysicalQuantity = dimensions::quantity::SpecificEnthalpy;
using Support = DomainSupport<utils::domain::Stellar>;
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
static constexpr std::string_view symbol = "h";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
static constexpr bool constraintsAreValid =
validate_constraints(Constraints{});
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
@@ -268,33 +304,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<Scalar>,
Operand<Density::Scalar>>;
using EosClosureSource =
FormSpec<quadrature::Term::eos_closure, 1, Operand<Scalar>, Operand<Density::Scalar>>;
// (h, q_h) contribution to
// h + phi - Psi_rotation - C = 0.
using EquilibriumEnthalpy = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
0,
Operand<Scalar>,
Operand<Scalar>>;
using EquilibriumEnthalpy =
FormSpec<quadrature::Term::hydrostatic_equilibrium, 0, Operand<Scalar>, Operand<Scalar>>;
// (phi, q_h) contribution to hydrostatic equilibrium.
using EquilibriumGravity = FormSpec<
quadrature::Term::hydrostatic_equilibrium,
0,
Operand<Gravity::Potential>,
Operand<Scalar>>;
using EquilibriumGravity =
FormSpec<quadrature::Term::hydrostatic_equilibrium, 0, Operand<Gravity::Potential>, Operand<Scalar>>;
// (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<Scalar>>;
using EquilibriumRotation = FormSpec<quadrature::Term::hydrostatic_equilibrium, 1, Operand<Scalar>>;
// (C, q_h), where C is spatially constant.
using EquilibriumConstant = FormSpec<
@@ -305,18 +329,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<Scalar>,
Operand<Scalar>>;
using IsobaricSurface = FormSpec<quadrature::Term::isobaric_surface, 0, Operand<Scalar>, Operand<Scalar>>;
// 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<Scalar>>;
using PressureIntegral = FormSpec<quadrature::Term::pressure_integral, 1, Operand<Scalar>>;
// Weak pressure force in the displacement test space:
//
@@ -331,11 +348,7 @@ export namespace mean_field::field {
Operand<Scalar>,
Operand<Displacement::Vector, FieldOperation::Gradient>>;
using ErrorNorm = FormSpec<
quadrature::Term::error_norm,
0,
Operand<Scalar>,
Operand<Scalar>>;
using ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Scalar>, Operand<Scalar>>;
};
using FormList = TypeList<
@@ -360,9 +373,10 @@ export namespace mean_field::field {
typename T::Quantities;
typename T::Constraints;
typename T::FormList;
typename T::Support;
{ T::name } -> std::convertible_to<std::string_view>;
} && isRegisteredQuantityList<typename T::Quantities> &&
} && IsFieldSupport<typename T::Support> && isRegisteredQuantityList<typename T::Quantities> &&
isFieldFormList<typename T::FormList>;
static_assert(FieldTag<Gravity>);
@@ -376,4 +390,22 @@ export namespace mean_field::field {
static_assert(std::same_as<
RelationTargetT<Gravity::Flux>,
Gravity::Potential>);
static_assert(std::same_as<
FieldDomainT<Density>,
utils::domain::Stellar>);
static_assert(std::same_as<
FieldDomainT<Enthalpy>,
utils::domain::Stellar>);
static_assert(std::same_as<
FieldDomainT<Gravity>,
utils::domain::All>);
static_assert(std::same_as<
FieldDomainT<Displacement>,
utils::domain::All>);
static_assert(NonSpatialField<BarotropicConstant>);
} // namespace mean_field::field

View File

@@ -6,7 +6,11 @@ import :mapping.domain_mapper;
export namespace mean_field::integrators {
class AdvectionIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
explicit AdvectionIntegrator(const mapping::DomainMapper &map);
AdvectionIntegrator(
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate
);
void AssembleElementVector(
const mfem::Array<const mfem::FiniteElement *> &el,
@@ -23,6 +27,6 @@ export namespace mean_field::integrators {
) override;
private:
const mapping::DomainMapper &m_map;
mapping::GridFunctionMappingEvaluator m_mapping;
};
} // namespace mean_field::integrators

View File

@@ -4,11 +4,12 @@ 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,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
);
@@ -30,7 +31,7 @@ export namespace mean_field::integrators {
) override;
private:
const mapping::DomainMapper &m_map;
mapping::GridFunctionMappingEvaluator m_mapping;
mfem::Vector m_omega;
const mfem::IntegrationRule *m_ir = nullptr;
};

View File

@@ -7,7 +7,9 @@ export namespace mean_field::integrators {
class CoriolisIntegrator : public mfem::BlockNonlinearFormIntegrator {
public:
CoriolisIntegrator(
const mapping::DomainMapper &map,
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
const mfem::Vector &omega
);
@@ -26,7 +28,7 @@ export namespace mean_field::integrators {
) override;
private:
const mapping::DomainMapper &m_map;
mapping::GridFunctionMappingEvaluator m_mapping;
mfem::Vector m_omega;
mfem::DenseMatrix m_omega_mat;
};

View File

@@ -5,19 +5,15 @@ 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
const mapping::DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
GravityForceJacobianMode jacobian_mode = GravityForceJacobianMode::field_coupled
);
void SetJacobianMode(GravityForceJacobianMode jacobian_mode);
@@ -39,7 +35,7 @@ export namespace mean_field::integrators {
) override;
private:
const mapping::DomainMapper &m_map;
mapping::GridFunctionMappingEvaluator m_mapping;
GravityForceJacobianMode m_jacobian_mode;
const mfem::IntegrationRule *m_integration_rule{nullptr};
};

Some files were not shown because too many files have changed in this diff Show More