Compare commits
8 Commits
dc912fd15e
...
76818f2f82
| Author | SHA1 | Date | |
|---|---|---|---|
| 76818f2f82 | |||
| 71423d543f | |||
| 25510008dd | |||
| 85500fef3b | |||
| 0a7f18c5c7 | |||
| 36adfa1174 | |||
| 177ae8b38a | |||
| 0f3ca8050b |
250
CMakeLists.txt
250
CMakeLists.txt
@@ -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)
|
||||
|
||||
@@ -128,7 +128,7 @@ BreakFunctionDefinitionParameters: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
BreakTemplateDeclarations: MultiLine
|
||||
ColumnLimit: 80
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
||||
@@ -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.
|
||||
|
||||
781
experiments/coupled_gauge_modes.cpp
Normal file
781
experiments/coupled_gauge_modes.cpp
Normal 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, ¢ralEnthalpy, 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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -107,10 +106,8 @@ public:
|
||||
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 {
|
||||
@@ -119,9 +116,15 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
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"};
|
||||
|
||||
@@ -171,8 +174,8 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
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=");
|
||||
has_reporter = has_reporter || argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") ||
|
||||
argument.starts_with("--reporter=");
|
||||
}
|
||||
if (!has_reporter) {
|
||||
catch_arguments.emplace_back("--reporter");
|
||||
|
||||
@@ -52,14 +52,18 @@ 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
|
||||
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
|
||||
1218
experiments/full_stellar_preconditioning.cpp
Normal file
1218
experiments/full_stellar_preconditioning.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,6 @@
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
import experiment;
|
||||
@@ -35,28 +34,46 @@ 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;
|
||||
return 2 * std::max(fem.gravityPotentialFes->GetMaxElementOrder(), fem.gravityFluxFes->GetMaxElementOrder()) + 8;
|
||||
}
|
||||
|
||||
static mfem::Vector assemble_monopole_projection_rhs(
|
||||
@@ -65,20 +82,23 @@ static mfem::Vector assemble_monopole_projection_rhs(
|
||||
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);
|
||||
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);
|
||||
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 {
|
||||
@@ -151,40 +171,35 @@ static mfem::Vector project_monopole_gradient(
|
||||
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(
|
||||
@@ -196,21 +211,20 @@ static double mapped_hdiv_relative_gap(
|
||||
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);
|
||||
@@ -221,15 +235,17 @@ static AccuracyBudgetEnergies measure_stellar_energies(
|
||||
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);
|
||||
@@ -237,18 +253,21 @@ static AccuracyBudgetEnergies measure_stellar_energies(
|
||||
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);
|
||||
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,8 +283,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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,47 +332,35 @@ 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(
|
||||
@@ -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
|
||||
);
|
||||
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);
|
||||
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) /
|
||||
@@ -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) {
|
||||
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);
|
||||
}
|
||||
|
||||
241
experiments/gravity_completed_rigid_motion.cpp
Normal file
241
experiments/gravity_completed_rigid_motion.cpp
Normal 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"
|
||||
);
|
||||
}
|
||||
591
experiments/gravity_preconditioning.cpp
Normal file
591
experiments/gravity_preconditioning.cpp
Normal 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);
|
||||
}
|
||||
993
experiments/material_surface_preconditioning.cpp
Normal file
993
experiments/material_surface_preconditioning.cpp
Normal 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> ¶meters,
|
||||
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> ¶meterOverrides
|
||||
) {
|
||||
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> ¶meterOverrides
|
||||
) {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
454
experiments/preconditioning_diagnostics.cpp
Normal file
454
experiments/preconditioning_diagnostics.cpp
Normal 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';
|
||||
}
|
||||
}
|
||||
529
experiments/rigid_motion_null_space.cpp
Normal file
529
experiments/rigid_motion_null_space.cpp
Normal 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"
|
||||
);
|
||||
}
|
||||
531
experiments/stellar_null_space.cppm
Normal file
531
experiments/stellar_null_space.cppm
Normal 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
|
||||
50
extension_example/CMakeLists.txt
Normal file
50
extension_example/CMakeLists.txt
Normal 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 ()
|
||||
55
extension_example/README.md
Normal file
55
extension_example/README.md
Normal 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.
|
||||
43
extension_example/demo.cpp
Normal file
43
extension_example/demo.cpp
Normal 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;
|
||||
}
|
||||
403
extension_example/ideal_gas_radiation.cppm
Normal file
403
extension_example/ideal_gas_radiation.cppm
Normal 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 ¶meters() 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
|
||||
BIN
extension_example/manual/physics_developer_manual.pdf
Normal file
BIN
extension_example/manual/physics_developer_manual.pdf
Normal file
Binary file not shown.
1058
extension_example/manual/physics_developer_manual.tex
Normal file
1058
extension_example/manual/physics_developer_manual.tex
Normal file
File diff suppressed because it is too large
Load Diff
61
extension_example/rotating_stellar_model.cppm
Normal file
61
extension_example/rotating_stellar_model.cppm
Normal 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 ¶meters) {
|
||||
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
|
||||
292
extension_example/tests/ideal_gas_radiation.cpp
Normal file
292
extension_example/tests/ideal_gas_radiation.cpp
Normal 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
|
||||
);
|
||||
}
|
||||
67
extension_example/tests/rotating_stellar_model.cpp
Normal file
67
extension_example/tests/rotating_stellar_model.cpp
Normal 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
4
format
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
|
||||
345
libmeanfield/impl/deformation/nodal_radial_surface.cpp
Normal file
345
libmeanfield/impl/deformation/nodal_radial_surface.cpp
Normal 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 ¶meters,
|
||||
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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
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 ¶meters,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) 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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) const {
|
||||
requireParameterSize(parameters);
|
||||
requireParameterSize(parameterDirection);
|
||||
requireSurfaceDisplacementSize(surfaceDisplacementDual);
|
||||
requireParameterSize(parameterDualAction);
|
||||
|
||||
parameterDualAction = 0.0;
|
||||
}
|
||||
|
||||
void PreparedNodalRadialSurface::requireParameterSize(const mfem::Vector ¶meters) 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
|
||||
1061
libmeanfield/impl/deformation/radial_extensions.cpp
Normal file
1061
libmeanfield/impl/deformation/radial_extensions.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
67
libmeanfield/impl/models/polytropic.cpp
Normal file
67
libmeanfield/impl/models/polytropic.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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."
|
||||
|
||||
@@ -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
|
||||
|
||||
219
libmeanfield/impl/operators/contexts/pressure_force_context.cpp
Normal file
219
libmeanfield/impl/operators/contexts/pressure_force_context.cpp
Normal 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
|
||||
@@ -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
@@ -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 &
|
||||
|
||||
@@ -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."
|
||||
);
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
590
libmeanfield/impl/operators/prepared_angular_momentum.cpp
Normal file
590
libmeanfield/impl/operators/prepared_angular_momentum.cpp
Normal 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 ¢er = 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 ¢er = 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
564
libmeanfield/impl/operators/prepared_displacement_operator.cpp
Normal file
564
libmeanfield/impl/operators/prepared_displacement_operator.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
920
libmeanfield/impl/operators/prepared_mass_normalization.cpp
Normal file
920
libmeanfield/impl/operators/prepared_mass_normalization.cpp
Normal 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
|
||||
1145
libmeanfield/impl/operators/prepared_pressure_force.cpp
Normal file
1145
libmeanfield/impl/operators/prepared_pressure_force.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
873
libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp
Normal file
873
libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
77
libmeanfield/impl/preconditioning/gravity_field.cpp
Normal file
77
libmeanfield/impl/preconditioning/gravity_field.cpp
Normal 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
|
||||
530
libmeanfield/impl/profile.cpp
Normal file
530
libmeanfield/impl/profile.cpp
Normal 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 ®ion) 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
|
||||
248
libmeanfield/impl/seed/lane_emden.cpp
Normal file
248
libmeanfield/impl/seed/lane_emden.cpp
Normal 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
|
||||
219
libmeanfield/impl/seed/stellar_equilibrium_projection.cpp
Normal file
219
libmeanfield/impl/seed/stellar_equilibrium_projection.cpp
Normal 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
|
||||
638
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
Normal file
638
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
Normal 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
|
||||
@@ -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 ¤t_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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ®ion) noexcept;
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
111
libmeanfield/interface/deformation/descriptors.cppm
Normal file
111
libmeanfield/interface/deformation/descriptors.cppm
Normal 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
|
||||
870
libmeanfield/interface/deformation/domain_deformation.cppm
Normal file
870
libmeanfield/interface/deformation/domain_deformation.cppm
Normal 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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector &volumeDisplacement,
|
||||
mfem::Vector ¶meterDual
|
||||
) {
|
||||
{ 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 ¶meters,
|
||||
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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
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 ¶meters,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) 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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) 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 ¶meters,
|
||||
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 ¶meters) 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 ¶meters,
|
||||
mfem::Vector &volumeDisplacement
|
||||
) const {
|
||||
m_implementation->buildVolumeDisplacement(parameters, volumeDisplacement);
|
||||
}
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &volumeDisplacementDirection
|
||||
) const {
|
||||
m_implementation->applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
|
||||
}
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const {
|
||||
m_implementation->applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
|
||||
}
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) 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 ¶meters,
|
||||
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 ¶meters,
|
||||
mfem::Vector &volumeDisplacement
|
||||
) const override {
|
||||
m_preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement);
|
||||
}
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &volumeDisplacementDirection
|
||||
) const override {
|
||||
m_preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
|
||||
}
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const override {
|
||||
m_preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
|
||||
}
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &volumeDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) 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 ¶meters,
|
||||
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
|
||||
63
libmeanfield/interface/deformation/interior_extension.cppm
Normal file
63
libmeanfield/interface/deformation/interior_extension.cppm
Normal 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
|
||||
138
libmeanfield/interface/deformation/nodal_radial_surface.cppm
Normal file
138
libmeanfield/interface/deformation/nodal_radial_surface.cppm
Normal 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 ¶meters,
|
||||
mfem::Vector &surfaceDisplacement
|
||||
) const;
|
||||
|
||||
void applyJacobian(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
mfem::Vector &surfaceDisplacementDirection
|
||||
) const;
|
||||
|
||||
void applyJacobianTranspose(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDual
|
||||
) const;
|
||||
|
||||
void applyPullbackDerivative(
|
||||
const mfem::Vector ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector ¶meterDualAction
|
||||
) 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 ¶meters) 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
|
||||
302
libmeanfield/interface/deformation/radial_extensions.cppm
Normal file
302
libmeanfield/interface/deformation/radial_extensions.cppm
Normal 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
|
||||
57
libmeanfield/interface/deformation/surface_prescription.cppm
Normal file
57
libmeanfield/interface/deformation/surface_prescription.cppm
Normal 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 ¶meters,
|
||||
const mfem::Vector ¶meterDirection,
|
||||
const mfem::Vector &surfaceDisplacementDual,
|
||||
mfem::Vector &surfaceDisplacement,
|
||||
mfem::Vector ¶meterDual
|
||||
) {
|
||||
{ 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
|
||||
61
libmeanfield/interface/deformation/vacuum_extension.cppm
Normal file
61
libmeanfield/interface/deformation/vacuum_extension.cppm
Normal 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
|
||||
312
libmeanfield/interface/dimensions/quantities.cppm
Normal file
312
libmeanfield/interface/dimensions/quantities.cppm
Normal 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
|
||||
100
libmeanfield/interface/eos/concepts.cppm
Normal file
100
libmeanfield/interface/eos/concepts.cppm
Normal 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
|
||||
90
libmeanfield/interface/eos/evaluation.cppm
Normal file
90
libmeanfield/interface/eos/evaluation.cppm
Normal 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
|
||||
237
libmeanfield/interface/eos/polytropic.cppm
Normal file
237
libmeanfield/interface/eos/polytropic.cppm
Normal 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
|
||||
128
libmeanfield/interface/eos/pressure_surface.cppm
Normal file
128
libmeanfield/interface/eos/pressure_surface.cppm
Normal 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
|
||||
145
libmeanfield/interface/eos/quantities.cppm
Normal file
145
libmeanfield/interface/eos/quantities.cppm
Normal 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
|
||||
95
libmeanfield/interface/eos/relations.cppm
Normal file
95
libmeanfield/interface/eos/relations.cppm
Normal 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
|
||||
645
libmeanfield/interface/eos/runtime.cppm
Normal file
645
libmeanfield/interface/eos/runtime.cppm
Normal 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
|
||||
183
libmeanfield/interface/equilibrium/stellar_discretization.cppm
Normal file
183
libmeanfield/interface/equilibrium/stellar_discretization.cppm
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user